<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 5.4.0">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/Frog_32px_1177822_easyicon.net.ico">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/Frog_32px_1177822_easyicon.net.ico">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/Frog_16px_1177822_easyicon.net.ico">
  <link rel="mask-icon" href="/images/Frog_32px_1177822_easyicon.net.ico" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">
  <link rel="stylesheet" href="/lib/pace/pace-theme-minimal.min.css">
  <script src="/lib/pace/pace.min.js"></script>

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"hxy1997.xyz","root":"/","scheme":"Pisces","version":"7.8.0","exturl":false,"sidebar":{"position":"left","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":true,"pangu":false,"comments":{"style":"tabs","active":"valine","storage":true,"lazyload":true,"nav":null,"activeClass":"valine"},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"输入关键字","hits_empty":"没有找到与「${query}」相关搜索","hits_stats":"${hits} 条相关记录，共耗时 ${time} ms"}},"localsearch":{"enable":true,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.json"};
  </script>

  <meta name="description" content="俗话说：检验学习成果最直接的方式就是造轮子。网上搜索一番，发现前后端交互神器axios造的人挺多的，并且提供了很多重构思路，为了能够站在巨人的肩膀上，并且axios常用，易于理解，那就是它啦，使用TypeScript重构axios。重构之前，我们需要简单地做一些需求分析，看一下我们这次重构需要支持哪些 feature(特性)。 特性 在浏览器端使用 XMLHttpRequest 对象通讯 支持 P">
<meta property="og:type" content="article">
<meta property="og:title" content="TypeScript重构axios">
<meta property="og:url" content="https://hxy1997.xyz/2021/05/13/TypeScript%E9%87%8D%E6%9E%84axios/index.html">
<meta property="og:site_name" content="hxy的博客">
<meta property="og:description" content="俗话说：检验学习成果最直接的方式就是造轮子。网上搜索一番，发现前后端交互神器axios造的人挺多的，并且提供了很多重构思路，为了能够站在巨人的肩膀上，并且axios常用，易于理解，那就是它啦，使用TypeScript重构axios。重构之前，我们需要简单地做一些需求分析，看一下我们这次重构需要支持哪些 feature(特性)。 特性 在浏览器端使用 XMLHttpRequest 对象通讯 支持 P">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20210520111405.png">
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20210524221312.png">
<meta property="article:published_time" content="2021-05-13T06:30:00.000Z">
<meta property="article:modified_time" content="2021-06-24T15:38:25.380Z">
<meta property="article:author" content="hxy">
<meta property="article:tag" content="项目">
<meta property="article:tag" content="Typescript">
<meta property="article:tag" content="axios">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20210520111405.png">

<link rel="canonical" href="https://hxy1997.xyz/2021/05/13/TypeScript%E9%87%8D%E6%9E%84axios/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>TypeScript重构axios | hxy的博客</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

<link rel="alternate" href="/atom.xml" title="hxy的博客" type="application/atom+xml">
</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">hxy的博客</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">Mia san Mia!</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-about">

    <a href="/about/" rel="section"><i class="fa fa-user fa-fw"></i>关于</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>

  </li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div id="search-result">
  <div id="no-result">
    <i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
  </div>
</div>

    </div>
  </div>

</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>
  <div class="reading-progress-bar"></div>

  <a href="https://github.com/huxingyi1997" class="github-corner" title="Follow me on GitHub" aria-label="Follow me on GitHub" rel="noopener" target="_blank"><svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://hxy1997.xyz/2021/05/13/TypeScript%E9%87%8D%E6%9E%84axios/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/Robben.gif">
      <meta itemprop="name" content="hxy">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="hxy的博客">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          TypeScript重构axios
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2021-05-13 14:30:00" itemprop="dateCreated datePublished" datetime="2021-05-13T14:30:00+08:00">2021-05-13</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-06-24 23:38:25" itemprop="dateModified" datetime="2021-06-24T23:38:25+08:00">2021-06-24</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/web%E5%89%8D%E7%AB%AF/" itemprop="url" rel="index"><span itemprop="name">web前端</span></a>
                </span>
            </span>

          
            <span class="post-meta-item" title="热度" id="busuanzi_container_page_pv" style="display: none;">
              <span class="post-meta-item-icon">
                <i class="fa fa-eye"></i>
              </span>
              <span class="post-meta-item-text">热度：</span>
              <span id="busuanzi_value_page_pv"></span>
            </span>
  
  <span class="post-meta-item">
    
      <span class="post-meta-item-icon">
        <i class="far fa-comment"></i>
      </span>
      <span class="post-meta-item-text">Valine：</span>
    
    <a title="valine" href="/2021/05/13/TypeScript%E9%87%8D%E6%9E%84axios/#valine-comments" itemprop="discussionUrl">
      <span class="post-comments-count valine-comment-count" data-xid="/2021/05/13/TypeScript%E9%87%8D%E6%9E%84axios/" itemprop="commentCount"></span>
    </a>
  </span>
  
  

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
        <p>俗话说：检验学习成果最直接的方式就是造轮子。网上搜索一番，发现前后端交互神器<code>axios</code>造的人挺多的，并且提供了很多重构思路，为了能够站在巨人的肩膀上，并且<code>axios</code>常用，易于理解，那就是它啦，使用<code>TypeScript</code>重构<code>axios</code>。重构之前，我们需要简单地做一些需求分析，看一下我们这次重构需要支持哪些 feature(特性)。</p>
<h2 id="特性"><a href="#特性" class="headerlink" title="特性"></a>特性</h2><ul>
<li>在浏览器端使用 XMLHttpRequest 对象通讯</li>
<li>支持 Promise API</li>
<li>支持请求和响应的拦截器</li>
<li>支持请求数据和响应数据的转换</li>
<li>支持请求的取消</li>
<li>JSON 数据的自动转换</li>
<li>客户端防止 XSRF</li>
</ul>
<p>此外，我们还会支持一些 axios 库支持的一些其它的 feature。这里要注意的，我们这次重构不包括 axios 在 Node 中的实现，因为这部分我们在平时项目中应用的很少，还涉及到很多 Node.js 的知识。</p>
<p>那么接下来我们就开始初始化项目吧！</p>
<p>项目发布在我的<a target="_blank" rel="noopener" href="https://github.com/huxingyi1997/ts-axios">github</a>和<a target="_blank" rel="noopener" href="https://gitee.com/hxy1997/ts-axios">gitee</a>上了，可以直接看README.md说明部分使用。想要看分页文档可以参考<a href="https://hxy1997.xyz/ts-axios-doc/">TypeScript 从零实现 axios</a>，这个使用了Vue-press框架，看起来更舒服。</p>
<span id="more"></span>

<h1 id="1-初始化项目"><a href="#1-初始化项目" class="headerlink" title="1.初始化项目"></a>1.初始化项目</h1><h2 id="1-1-创建代码仓库"><a href="#1-1-创建代码仓库" class="headerlink" title="1.1 创建代码仓库"></a>1.1 创建代码仓库</h2><p>接下来，我们开始初始化项目，首先我们先去 GitHub 上创建一个 repo，填好 repo 名称，以及写一下 README，对项目先做个简单的描述。</p>
<p>通常我们初始化一个项目，需要配置一大堆东西，比如 <code>package.json</code>、<code>.editorconfig</code>、<code>.gitignore</code> 等；还包括一些构建工具如 <code>rollup</code>、<code>webpack</code> 以及它们的配置。</p>
<p>当我们使用 TypeScript 去写一个项目的时候，还需要配置 TypeScript 的编译配置文件 <code>tsconfig.json</code> 以及 <code>tslint.json</code> 文件。</p>
<p>这些茫茫多的配置往往会让一个想从零开始写项目的同学望而却步，如果有一个脚手架工具帮我们生成好这些初始化文件该多好。好在确实有这样的工具，接下来我们的主角 <code>TypeScript library starter</code> 隆重登场。</p>
<h2 id="1-2-TypeScript-library-starter"><a href="#1-2-TypeScript-library-starter" class="headerlink" title="1.2 TypeScript library starter"></a>1.2 TypeScript library starter</h2><p>它是一个开源的 TypeScript 开发基础库的脚手架工具，可以帮助我们快速初始化一个 TypeScript 项目，我们可以去它的<a target="_blank" rel="noopener" href="https://github.com/alexjoverm/typescript-library-starter">官网地址 </a>学习和使用它。</p>
<h3 id="1-2-1-使用方式"><a href="#1-2-1-使用方式" class="headerlink" title="1.2.1 使用方式"></a>1.2.1 使用方式</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> git@github.com:alexjoverm/typescript-library-starter.git ts-axios</span><br><span class="line"><span class="built_in">cd</span> ts-axios</span><br><span class="line"></span><br><span class="line">cnpm install</span><br></pre></td></tr></table></figure>

<p>先通过 <code>git clone</code> 把项目代码拉下来到我们的 <code>ts-axios</code> 目录，然后运行 <code>npm install/cnpm install</code> 安装依赖，并且给项目命名，我们仍然使用 <code>ts-axios</code>。</p>
<p>安装好依赖后，我们先来预览一下这个项目的目录结构。</p>
<h3 id="1-2-2-目录文件介绍"><a href="#1-2-2-目录文件介绍" class="headerlink" title="1.2.2 目录文件介绍"></a>1.2.2 目录文件介绍</h3><p><code>TypeScript library starter</code> 生成的目录结构如下：</p>
<figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">├── CONTRIBUTING.md</span><br><span class="line">├── LICENSE </span><br><span class="line">├── README.md</span><br><span class="line">├── code-of-conduct.md</span><br><span class="line">├── node_modules</span><br><span class="line">├── package-lock.json</span><br><span class="line">├── package.json</span><br><span class="line">├── rollup.config.ts // rollup 配置文件</span><br><span class="line">├── src // 源码目录</span><br><span class="line">├── test // 测试目录</span><br><span class="line">├── tools // 发布到 GitHup pages 以及 发布到 npm 的一些配置脚本工具</span><br><span class="line">├── tsconfig.json // TypeScript 编译配置文件</span><br><span class="line">└── tslint.json // TypeScript lint 文件</span><br></pre></td></tr></table></figure>

<h3 id="1-2-3-优秀工具集成"><a href="#1-2-3-优秀工具集成" class="headerlink" title="1.2.3 优秀工具集成"></a>1.2.3 优秀工具集成</h3><p>使用 <code>TypeScript library starter</code> 创建的项目集成了很多优秀的开源工具：</p>
<ul>
<li>使用 <a target="_blank" rel="noopener" href="https://rollupjs.org/">RollupJS</a>帮助我们打包。</li>
<li>使用 <a target="_blank" rel="noopener" href="https://github.com/prettier/prettier">Prettier</a>和 <a target="_blank" rel="noopener" href="https://palantir.github.io/tslint/">TSLint</a>帮助我们格式化代码以及保证代码风格一致性。</li>
<li>使用 <a target="_blank" rel="noopener" href="https://typedoc.org/">TypeDoc</a>帮助我们自动生成文档并部署到 GitHub pages。</li>
<li>使用 <a target="_blank" rel="noopener" href="https://jestjs.io/">Jest</a>帮助我们做单元测试。</li>
<li>使用 <a target="_blank" rel="noopener" href="https://github.com/commitizen/cz-cli">Commitizen</a>帮助我们生成规范化的提交注释。</li>
<li>使用 <a target="_blank" rel="noopener" href="https://github.com/semantic-release/semantic-release">Semantic release</a>帮助我们管理版本和发布。</li>
<li>使用 <a target="_blank" rel="noopener" href="https://github.com/typicode/husky">husky</a>帮助我们更简单地使用 git hooks。</li>
<li>使用 <a target="_blank" rel="noopener" href="https://github.com/conventional-changelog/conventional-changelog">Conventional changelog</a>帮助我们通过代码提交信息自动生成 change log。</li>
</ul>
<p>这里我们列举了很多工具，感兴趣的同学们可以点开他们的链接对这些工具做进一步学习。</p>
<h3 id="1-2-4-Npm-Scripts"><a href="#1-2-4-Npm-Scripts" class="headerlink" title="1.2.4 Npm Scripts"></a>1.2.4 Npm Scripts</h3><p><code>TypeScript library starter</code> 同样在 <code>package.json</code> 中帮我们配置了一些 <code>npm scripts</code>，接下来我们先列举一下我们开发中常用的 <code>npm scripts</code>，剩余的我们在之后学习中遇到的时候再来介绍。</p>
<ul>
<li><code>npm run lint</code>: 使用 TSLint 工具检查 <code>src</code> 和 <code>test</code> 目录下 TypeScript 代码的可读性、可维护性和功能性错误。</li>
<li><code>npm start</code>: 观察者模式运行 <code>rollup</code> 工具打包代码。</li>
<li><code>npm test</code>: 运行 <code>jest</code> 工具跑单元测试。</li>
<li><code>npm run commit</code>: 运行 <code>commitizen</code> 工具提交格式化的 <code>git commit</code> 注释。</li>
<li><code>npm run build</code>: 运行 <code>rollup</code> 编译打包 TypeScript 代码，并运行 <code>typedoc</code> 工具生成文档。</li>
</ul>
<h2 id="1-3-关联远程分支"><a href="#1-3-关联远程分支" class="headerlink" title="1.3 关联远程分支"></a>1.3 关联远程分支</h2><p>代码已经初始化好，接下来我们要把当前代码仓库关联我们的远程仓库，首先在命令行中运行命令查看远程分支：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git remote -v</span><br></pre></td></tr></table></figure>

<p>这里我们不会得到任何输出，因为我们还没有关联远程分支，我们先去 GitHub 上找到我们仓库的地址，在命令行运行：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git remote add origin git@github.com:&#123;&#123;yourGithubName&#125;&#125;/ts-axios.git</span><br></pre></td></tr></table></figure>

<p>关联后，远程库的名字就是 <code>origin</code>，这是 <code>Git</code> 默认的叫法，也可以改成别的，但是 <code>origin</code> 这个名字一看就知道是远程库。</p>
<p>接着你就可以继续运行 <code>git remote -v</code> 查看关联结果了。</p>
<h3 id="1-3-1-拉取代码"><a href="#1-3-1-拉取代码" class="headerlink" title="1.3.1 拉取代码"></a>1.3.1 拉取代码</h3><p>运行如下命令从远程仓库拉取 master 分支代码并合并：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git pull origin master</span><br></pre></td></tr></table></figure>

<p>这个时候会报错：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">error: The following untracked working tree files would be overwritten by merge:</span><br><span class="line">	README.md</span><br><span class="line">Please move or remove them before you merge.</span><br><span class="line">Aborting</span><br></pre></td></tr></table></figure>

<p>因为我们在使用 <code>typescript library starter</code> 初始化代码的时候也创建了 <code>README.md</code>，和远程仓库的 <code>README.md</code> 冲突了。我们把 <code>README.md</code> 文件删除，再次运行：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git pull origin master</span><br></pre></td></tr></table></figure>

<p>这次代码就拉取成功了，并且在本地也创建了一个 <code>master</code> 分支。</p>
<p>我是没有成功，一直报错<code>fatal: Couldn&#39;t find remote ref master</code>，删了.git文件夹然后重新git init才创建成功，其实是远程仓库初始化没创建mater分支所致</p>
<h3 id="1-3-2-提交代码"><a href="#1-3-2-提交代码" class="headerlink" title="1.3.2 提交代码"></a>1.3.2 提交代码</h3><p>最后我们来提交代码，首先运行：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git add .</span><br></pre></td></tr></table></figure>

<p>把提交的代码从工作区添加到暂存区，然后运行 <code>npm run commit</code> 这个 <code>npm</code> 脚本来提交代码，运行后它会依次询问你几个问题，比如你这次修改的范围包括哪些、提交的描述、是否有 break change、影响了哪些 issue 等等。</p>
<p>填写完毕，工具会帮我们运行 <code>git commit</code> 并且自动把我们提交的信息合成一条提交注释。接着运行命令把代码推送到远程 git 仓库中：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push origin master</span><br></pre></td></tr></table></figure>

<p>接着我们去 GitHub 仓库中就可以看到刚才这条提交记录了。</p>
<p>至此，我们项目已经初始化完毕，接下来我们就开始编写源码实现 axios 了。</p>
<h1 id="2-编写基础请求代码"><a href="#2-编写基础请求代码" class="headerlink" title="2.编写基础请求代码"></a>2.编写基础请求代码</h1><p>我们这节课开始编写 <code>ts-axios</code> 库，我们的目标是实现简单的发送请求功能，即客户端通过 <code>XMLHttpRequest</code> 对象把请求发送到 server 端，server 端能收到请求并响应即可。</p>
<p>我们实现 <code>axios</code> 最基本的操作，通过传入一个对象发送请求，如下：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/simple/get&#x27;</span>,</span><br><span class="line">  params: &#123;</span><br><span class="line">    a: <span class="number">1</span>,</span><br><span class="line">    b: <span class="number">2</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<h2 id="2-1-创建入口文件"><a href="#2-1-创建入口文件" class="headerlink" title="2.1 创建入口文件"></a>2.1 创建入口文件</h2><p>我们删除 <code>src</code> 目录下的文件，先创建一个 <code>index.ts</code> 文件，作为整个库的入口文件，然后我们先定义一个 <code>axios</code> 方法，并把它导出，如下：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">axios</span>(<span class="params">config</span>) </span>&#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> axios</span><br></pre></td></tr></table></figure>

<p>这里 TypeScript 编译器会检查到错误，分别是 <code>config</code> 的声明上有隐含的 <code>any</code> 报错，以及代码块为空。代码块为空我们比较好理解，第一个错误的原因是因为我们给 TypeScript 编译配置的 <code>strict</code> 设置为 <code>true</code> 导致。</p>
<h3 id="2-1-1-编译配置文件-tsconfig-json"><a href="#2-1-1-编译配置文件-tsconfig-json" class="headerlink" title="2.1.1 编译配置文件 tsconfig.json"></a>2.1.1 编译配置文件 tsconfig.json</h3><p><code>tsconfig.json</code> 文件中指定了用来编译这个项目的根文件和编译选项，关于它的具体学习，我希望同学们去<a target="_blank" rel="noopener" href="https://www.typescriptlang.org/docs/handbook/tsconfig-json.html">官网</a>系统学习一下</p>
<p>我们在之前讲 TypeScript 的基础时，会运行 <code>tsc</code> 命令去编译 TypeScript 文件，编译器会从当前目录开始去查找 <code>tsconfig.json</code> 文件，作为编译时的一些编译选项。</p>
<p>我们来看一下 tsconfig.json 文件，它包含了很多编译时的配置，其中我们把 <code>strict</code> 设置为 <code>true</code>，它相当于启用所有严格类型的检查选项。启用 <code>--strict</code> 相当于启用 <code>--noImplicitAny</code>,<code>--noImplicitThis</code>,<code>--alwaysStrict</code>，<code>--strictNullChecks</code> 和 <code>--strictFunctionTypes</code> 和 <code>--strictPropertyInitialization</code>。</p>
<p>我们讲 TypeScript 的基础时提到了 <code>--strictNullChecks</code>，其它的编译配置我建议同学们都去查看它的<a target="_blank" rel="noopener" href="https://www.typescriptlang.org/docs/handbook/compiler-options.html">官网文档</a>，把它当做手册去查阅即可。</p>
<h3 id="2-1-2-定义-AxiosRequestConfig-接口类型"><a href="#2-1-2-定义-AxiosRequestConfig-接口类型" class="headerlink" title="2.1.2 定义 AxiosRequestConfig 接口类型"></a>2.1.2 定义 AxiosRequestConfig 接口类型</h3><p>接下来，我们需要给 <code>config</code> 参数定义一种接口类型。我们创建一个 <code>types</code> 目录，在下面创建一个 <code>index.ts</code> 文件，作为我们项目中公用的类型定义文件。</p>
<p>接下来我们来定义 <code>AxiosRequestConfig</code> 接口类型：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosRequestConfig &#123;</span><br><span class="line">  url: <span class="built_in">string</span></span><br><span class="line">  method?: <span class="built_in">string</span></span><br><span class="line">  data?: <span class="built_in">any</span></span><br><span class="line">  params?: <span class="built_in">any</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>其中，<code>url</code> 为请求的地址，必选属性；而其余属性都是可选属性。<code>method</code> 是请求的 HTTP 方法；<code>data</code> 是 <code>post</code>、<code>patch</code> 等类型请求的数据，放到 <code>request body</code> 中的；<code>params</code> 是 <code>get</code>、<code>head</code> 等类型请求的数据，拼接到 <code>url</code> 的 <code>query string</code> 中的。</p>
<p>为了让 <code>method</code> 只能传入合法的字符串，我们定义一种字符串字面量类型 <code>Method</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">type</span> Method = <span class="string">&#x27;get&#x27;</span> | <span class="string">&#x27;GET&#x27;</span></span><br><span class="line">  | <span class="string">&#x27;delete&#x27;</span> | <span class="string">&#x27;Delete&#x27;</span></span><br><span class="line">  | <span class="string">&#x27;head&#x27;</span> | <span class="string">&#x27;HEAD&#x27;</span></span><br><span class="line">  | <span class="string">&#x27;options&#x27;</span> | <span class="string">&#x27;OPTIONS&#x27;</span></span><br><span class="line">  | <span class="string">&#x27;post&#x27;</span> | <span class="string">&#x27;POST&#x27;</span></span><br><span class="line">  | <span class="string">&#x27;put&#x27;</span> | <span class="string">&#x27;PUT&#x27;</span></span><br><span class="line">  | <span class="string">&#x27;patch&#x27;</span> | <span class="string">&#x27;PATCH&#x27;</span></span><br></pre></td></tr></table></figure>

<p>接着我们把 <code>AxiosRequestConfig</code> 中的 <code>method</code> 属性类型改成这种字符串字面量类型：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosRequestConfig &#123;</span><br><span class="line">  url: <span class="built_in">string</span></span><br><span class="line">  method?: Method</span><br><span class="line">  data?: <span class="built_in">any</span></span><br><span class="line">  params?: <span class="built_in">any</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>然后回到 <code>index.ts</code>，我们引入 <code>AxiosRequestConfig</code> 类型，作为 <code>config</code> 的参数类型，如下：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; AxiosRequestConfig &#125; <span class="keyword">from</span> <span class="string">&#x27;./types&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">axios</span>(<span class="params">config: AxiosRequestConfig</span>) </span>&#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> axios</span><br></pre></td></tr></table></figure>

<p>那么接下来，我们就来实现这个函数体内部的逻辑——发送请求。</p>
<h2 id="2-2-利用-XMLHttpRequest-发送请求"><a href="#2-2-利用-XMLHttpRequest-发送请求" class="headerlink" title="2.2 利用 XMLHttpRequest 发送请求"></a>2.2 利用 XMLHttpRequest 发送请求</h2><p>我们并不想在 <code>index.ts</code> 中去实现发送请求的逻辑，我们利用模块化的编程思想，把这个功能拆分到一个单独的模块中。</p>
<p>于是我们在 <code>src</code> 目录下创建一个 <code>xhr.ts</code> 文件，我们导出一个 <code>xhr</code> 方法，它接受一个 <code>config</code> 参数，类型也是 <code>AxiosRequestConfig</code> 类型。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; AxiosRequestConfig &#125; <span class="keyword">from</span> <span class="string">&#x27;./types&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="function"><span class="keyword">function</span> <span class="title">xhr</span>(<span class="params">config: AxiosRequestConfig</span>) </span>&#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们首先通过解构赋值的语法从 <code>config</code> 中拿到对应的属性值赋值给我的变量，并且还定义了一些默认值，因为在 <code>AxiosRequestConfig</code> 接口的定义中，有些属性是可选的。</p>
<p>接着我们实例化了一个 <code>XMLHttpRequest</code> 对象，然后调用了它的 <code>open</code> 方法，传入了对应的一些参数，最后调用 <code>send</code> 方法发送请求。</p>
<p>对于 <code>XMLHttpRequest</code> 的学习，我希望同学们去 <a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest">MDN</a>上系统地学习一下它的一些属性和方法，当做参考资料，因为在后续的开发中我们可能会反复查阅这些文档资料。</p>
<h3 id="2-2-1-引入-xhr-模块"><a href="#2-2-1-引入-xhr-模块" class="headerlink" title="2.2.1 引入 xhr 模块"></a>2.2.1 引入 xhr 模块</h3><p>编写好了 <code>xhr</code> 模块，我们就需要在 <code>index.ts</code> 中去引入这个模块，如下：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; AxiosRequestConfig &#125; <span class="keyword">from</span> <span class="string">&#x27;./types&#x27;</span></span><br><span class="line"><span class="keyword">import</span> xhr <span class="keyword">from</span> <span class="string">&#x27;./xhr&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">axios</span>(<span class="params">config: AxiosRequestConfig</span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">  xhr(config)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> axios</span><br></pre></td></tr></table></figure>

<p>那么至此，我们基本的发送请求代码就编写完毕了，接下来我们来写一个小 demo，来使用我们编写的 axios 库去发送请求。</p>
<h2 id="2-3-demo-编写"><a href="#2-3-demo-编写" class="headerlink" title="2.3 demo 编写"></a>2.3 demo 编写</h2><p>我们会利用 Node.js 的 <a target="_blank" rel="noopener" href="http://expressjs.com/"><code>express</code></a>库去运行我们的demo，利用 <a target="_blank" rel="noopener" href="https://webpack.js.org/"><code>webpack</code></a> 来作为 demo 的构建工具。</p>
<h3 id="2-3-1-依赖安装"><a href="#2-3-1-依赖安装" class="headerlink" title="2.3.1 依赖安装"></a>2.3.1 依赖安装</h3><p>我们先来安装一些编写 demo 需要的依赖包，如下：</p>
<figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&quot;webpack&quot;: &quot;^5.24.0&quot;,</span><br><span class="line">&quot;webpack-dev-middleware&quot;: &quot;^4.1.0&quot;,</span><br><span class="line">&quot;webpack-hot-middleware&quot;: &quot;^2.25.0&quot;,</span><br><span class="line">&quot;ts-loader&quot;: &quot;^8.0.17&quot;,</span><br><span class="line">&quot;tslint-loader&quot;: &quot;^3.5.4&quot;,</span><br><span class="line">&quot;express&quot;: &quot;^4.17.1&quot;,</span><br><span class="line">&quot;body-parser&quot;: &quot;^1.19.0&quot;</span><br></pre></td></tr></table></figure>

<p>其中，<code>webpack</code> 是打包构建工具，<code>webpack-dev-middleware</code> 和 <code>webpack-hot-middleware</code> 是 2 个 <code>express</code> 的 <code>webpack</code> 中间件，<code>ts-loader</code> 和 <code>tslint-loader</code> 是 <code>webpack</code> 需要的 TypeScript 相关 loader，<code>express</code> 是 Node.js 的服务端框架，<code>body-parser</code> 是 <code>express</code> 的一个中间件，解析 <code>body</code> 数据用的。</p>
<p>我是手动安装更新的，可以在<code>package.json</code>中复制，然后一并安装</p>
<h3 id="2-3-2-编写-webpack-配置文件"><a href="#2-3-2-编写-webpack-配置文件" class="headerlink" title="2.3.2 编写 webpack 配置文件"></a>2.3.2 编写 webpack 配置文件</h3><p>在 <code>examples</code> 目录下创建 <code>webpack</code> 配置文件 <code>webpack.config.js</code>：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">&#x27;path&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> webpack = <span class="built_in">require</span>(<span class="string">&#x27;webpack&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  mode: <span class="string">&#x27;development&#x27;</span>,</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 我们会在 examples 目录下建多个子目录</span></span><br><span class="line"><span class="comment">   * 我们会把不同章节的 demo 放到不同的子目录中</span></span><br><span class="line"><span class="comment">   * 每个子目录的下会创建一个 app.ts</span></span><br><span class="line"><span class="comment">   * app.ts 作为 webpack 构建的入口文件</span></span><br><span class="line"><span class="comment">   * entries 收集了多目录个入口文件，并且每个入口还引入了一个用于热更新的文件</span></span><br><span class="line"><span class="comment">   * entries 是一个对象，key 为目录名</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  entry: fs.readdirSync(__dirname).reduce(<span class="function">(<span class="params">entries, dir</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> fullDir = path.join(__dirname, dir)</span><br><span class="line">    <span class="keyword">const</span> entry = path.join(fullDir, <span class="string">&#x27;app.ts&#x27;</span>)</span><br><span class="line">    <span class="keyword">if</span> (fs.statSync(fullDir).isDirectory() &amp;&amp; fs.existsSync(entry)) &#123;</span><br><span class="line">      entries[dir] = [<span class="string">&#x27;webpack-hot-middleware/client&#x27;</span>, entry]</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> entries</span><br><span class="line">  &#125;, &#123;&#125;),</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 根据不同的目录名称，打包生成目标 js，名称和目录名一致</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  output: &#123;</span><br><span class="line">    path: path.join(__dirname, <span class="string">&#x27;__build__&#x27;</span>),</span><br><span class="line">    filename: <span class="string">&#x27;[name].js&#x27;</span>,</span><br><span class="line">    publicPath: <span class="string">&#x27;/__build__/&#x27;</span></span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="built_in">module</span>: &#123;</span><br><span class="line">    rules: [</span><br><span class="line">      &#123;</span><br><span class="line">        test: <span class="regexp">/\.ts$/</span>,</span><br><span class="line">        enforce: <span class="string">&#x27;pre&#x27;</span>,</span><br><span class="line">        use: [</span><br><span class="line">          &#123;</span><br><span class="line">            loader: <span class="string">&#x27;tslint-loader&#x27;</span></span><br><span class="line">          &#125;</span><br><span class="line">        ]</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        test: <span class="regexp">/\.tsx?$/</span>,</span><br><span class="line">        use: [</span><br><span class="line">          &#123;</span><br><span class="line">            loader: <span class="string">&#x27;ts-loader&#x27;</span>,</span><br><span class="line">            options: &#123;</span><br><span class="line">              transpileOnly: <span class="literal">true</span></span><br><span class="line">            &#125;</span><br><span class="line">          &#125;</span><br><span class="line">        ]</span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  resolve: &#123;</span><br><span class="line">    extensions: [<span class="string">&#x27;.ts&#x27;</span>, <span class="string">&#x27;.tsx&#x27;</span>, <span class="string">&#x27;.js&#x27;</span>]</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  plugins: [</span><br><span class="line">    <span class="keyword">new</span> webpack.HotModuleReplacementPlugin(),</span><br><span class="line">    <span class="keyword">new</span> webpack.NoEmitOnErrorsPlugin()</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="2-3-3-编写-server-文件"><a href="#2-3-3-编写-server-文件" class="headerlink" title="2.3.3 编写 server 文件"></a>2.3.3 编写 server 文件</h3><p>在 <code>examples</code> 目录下创建 <code>server.js</code> 文件：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">&#x27;express&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> bodyParser = <span class="built_in">require</span>(<span class="string">&#x27;body-parser&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> webpack = <span class="built_in">require</span>(<span class="string">&#x27;webpack&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> webpackDevMiddleware = <span class="built_in">require</span>(<span class="string">&#x27;webpack-dev-middleware&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> webpackHotMiddleware = <span class="built_in">require</span>(<span class="string">&#x27;webpack-hot-middleware&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> WebpackConfig = <span class="built_in">require</span>(<span class="string">&#x27;./webpack.config&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = express()</span><br><span class="line"><span class="keyword">const</span> compiler = webpack(WebpackConfig)</span><br><span class="line"></span><br><span class="line">app.use(webpackDevMiddleware(compiler, &#123;</span><br><span class="line">  publicPath: <span class="string">&#x27;/__build__/&#x27;</span>,</span><br><span class="line">  stats: &#123;</span><br><span class="line">    colors: <span class="literal">true</span>,</span><br><span class="line">    chunks: <span class="literal">false</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;))</span><br><span class="line"></span><br><span class="line">app.use(webpackHotMiddleware(compiler))</span><br><span class="line"></span><br><span class="line">app.use(express.static(__dirname))</span><br><span class="line"></span><br><span class="line">app.use(bodyParser.json())</span><br><span class="line">app.use(bodyParser.urlencoded(&#123; <span class="attr">extended</span>: <span class="literal">true</span> &#125;))</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> port = process.env.PORT || <span class="number">8080</span></span><br><span class="line"><span class="built_in">module</span>.exports = app.listen(port, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">`Server listening on http://localhost:<span class="subst">$&#123;port&#125;</span>, Ctrl+C to stop`</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<h3 id="2-3-4-编写-demo-代码"><a href="#2-3-4-编写-demo-代码" class="headerlink" title="2.3.4 编写 demo 代码"></a>2.3.4 编写 demo 代码</h3><p>首先在 <code>examples</code> 目录下创建 <code>index.html</code> 和 <code>global.css</code>，作为所有 <code>demo</code> 的入口文件已全局样式文件。</p>
<p><code>index.html</code>：</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>ts-axios examples<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;stylesheet&quot;</span> <span class="attr">href</span>=<span class="string">&quot;/global.css&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span> <span class="attr">style</span>=<span class="string">&quot;padding: 0 20px&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">h1</span>&gt;</span>ts-axios examples<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">ul</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;simple&quot;</span>&gt;</span>Simple<span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>为了方便未来的测试，可以将所有后面需要用到的目录写在这里</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>ts-axios examples<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;stylesheet&quot;</span> <span class="attr">href</span>=<span class="string">&quot;/global.css&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span> <span class="attr">style</span>=<span class="string">&quot;padding: 0 20px&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">h1</span>&gt;</span>ts-axios examples<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">ul</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;simple&quot;</span>&gt;</span>Simple<span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;base&quot;</span>&gt;</span>Base<span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;error&quot;</span>&gt;</span>Error<span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;extend&quot;</span>&gt;</span>Extend<span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;interceptor&quot;</span>&gt;</span>Interceptor<span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;config&quot;</span>&gt;</span>Config<span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;cancel&quot;</span>&gt;</span>Cancel<span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;more&quot;</span>&gt;</span>More<span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;upload-download&quot;</span>&gt;</span>Upload<span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p><code>global.css</code>：</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">html</span>, <span class="selector-tag">body</span> &#123;</span><br><span class="line">  <span class="attribute">font-family</span>: -apple-system, BlinkMacSystemFont, <span class="string">&quot;Segoe UI&quot;</span>, Roboto, Helvetica, Arial, sans-serif, <span class="string">&quot;Apple Color Emoji&quot;</span>, <span class="string">&quot;Segoe UI Emoji&quot;</span>, <span class="string">&quot;Segoe UI Symbol&quot;</span>;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#2c3e50</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-tag">ul</span> &#123;</span><br><span class="line">  <span class="attribute">line-height</span>: <span class="number">1.5em</span>;</span><br><span class="line">  <span class="attribute">padding-left</span>: <span class="number">1.5em</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-tag">a</span> &#123;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#7f8c8d</span>;</span><br><span class="line">  <span class="attribute">text-decoration</span>: none;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-tag">a</span><span class="selector-pseudo">:hover</span> &#123;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#4fc08d</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>然后在 <code>examples</code> 目录下创建 <code>simple</code> 目录，作为本章节的 demo 目录，在该目录下再创建 <code>index.html</code> 和 <code>app.ts</code> 文件</p>
<p><code>index.html</code> 文件如下:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Simple example<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;/__build__/simple.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p><code>app.ts</code> 文件如下：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;../../src/index&#x27;</span></span><br><span class="line"></span><br><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/simple/get&#x27;</span>,</span><br><span class="line">  params: &#123;</span><br><span class="line">    a: <span class="number">1</span>,</span><br><span class="line">    b: <span class="number">2</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>因为我们这里通过 <code>axios</code> 发送了请求，那么我们的 server 端要实现对应的路由接口，我们来修改 <code>server.js</code>，添加如下代码：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> router = express.Router()</span><br><span class="line"></span><br><span class="line">router.get(<span class="string">&#x27;/simple/get&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  res.json(&#123;</span><br><span class="line">    msg: <span class="string">`hello world`</span></span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">app.use(router)</span><br></pre></td></tr></table></figure>

<h3 id="2-3-5-运行-demo"><a href="#2-3-5-运行-demo" class="headerlink" title="2.3.5 运行 demo"></a>2.3.5 运行 demo</h3><p>接着我们在 <code>package.json</code> 中去新增一个 <code>npm script</code>：</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&quot;dev&quot;: &quot;node examples/server.js&quot;</span><br></pre></td></tr></table></figure>

<p>然后我们去控制台执行命令</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm run dev</span><br></pre></td></tr></table></figure>

<p>相当于执行了 <code>node examples/server.js</code>，会开启我们的 server。</p>
<p>接着我们打开 chrome 浏览器，访问 <code>http://localhost:8080/</code> 即可访问我们的 demo 了，我们点到 <code>Simple</code> 目录下，通过开发者工具的 network 部分我们可以看到成功发送到了一条请求，并在 response 中看到了服务端返回的数据。</p>
<p>至此，我们就实现了一个简单的请求发送，并编写了相关的 demo。但是现在存在一些问题：我们传入的 <code>params</code> 数据并没有用，也没有拼接到 <code>url</code> 上；我们对 request body 的数据格式、请求头 headers 也没有做处理；另外我们虽然从网络层面收到了响应的数据，但是我们代码层面也并没有对响应的数据做处理。那么下面一章，我们就来解决这些问题。</p>
<h1 id="3-处理请求-url-参数"><a href="#3-处理请求-url-参数" class="headerlink" title="3.处理请求 url 参数"></a>3.处理请求 url 参数</h1><h2 id="3-1-需求分析"><a href="#3-1-需求分析" class="headerlink" title="3.1 需求分析"></a>3.1 需求分析</h2><p>还记得我们上节课遗留了一个问题，再来看这个例子：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/get&#x27;</span>,</span><br><span class="line">  params: &#123;</span><br><span class="line">    a: <span class="number">1</span>,</span><br><span class="line">    b: <span class="number">2</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>我们希望最终请求的 <code>url</code> 是 <code>/base/get?a=1&amp;b=2</code>，这样服务端就可以通过请求的 url 解析到我们传来的参数数据了。实际上就是把 <code>params</code> 对象的 key 和 value 拼接到 <code>url</code> 上。</p>
<h3 id="3-1-1-参数值为数组"><a href="#3-1-1-参数值为数组" class="headerlink" title="3.1.1 参数值为数组"></a>3.1.1 参数值为数组</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/get&#x27;</span>,</span><br><span class="line">  params: &#123;</span><br><span class="line">    foo: [<span class="string">&#x27;bar&#x27;</span>, <span class="string">&#x27;baz&#x27;</span>]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>最终请求的 <code>url</code> 是 <code>/base/get?foo[]=bar&amp;foo[]=baz&#39;</code>。</p>
<h3 id="3-1-2-参数值为对象"><a href="#3-1-2-参数值为对象" class="headerlink" title="3.1.2 参数值为对象"></a>3.1.2 参数值为对象</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/get&#x27;</span>,</span><br><span class="line">  params: &#123;</span><br><span class="line">    foo: &#123;</span><br><span class="line">      bar: <span class="string">&#x27;baz&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>最终请求的 <code>url</code> 是 <code>/base/get?foo=%7B%22bar%22:%22baz%22%7D</code>，<code>foo</code> 后面拼接的是 <code>&#123;&quot;bar&quot;:&quot;baz&quot;&#125;</code> encode 后的结果。</p>
<h3 id="3-1-3-参数值为-Date-类型"><a href="#3-1-3-参数值为-Date-类型" class="headerlink" title="3.1.3 参数值为 Date 类型"></a>3.1.3 参数值为 Date 类型</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> date = <span class="keyword">new</span> <span class="built_in">Date</span>()</span><br><span class="line"></span><br><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/get&#x27;</span>,</span><br><span class="line">  params: &#123;</span><br><span class="line">    date</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>最终请求的 <code>url</code> 是 <code>/base/get?date=2019-04-01T05:55:39.030Z</code>，<code>date</code> 后面拼接的是 <code>date.toISOString()</code> 的结果。</p>
<h3 id="3-1-4-特殊字符支持"><a href="#3-1-4-特殊字符支持" class="headerlink" title="3.1.4 特殊字符支持"></a>3.1.4 特殊字符支持</h3><p>对于字符 <code>@</code>、<code>:</code>、<code>$</code>、<code>,</code>、<code>  </code>、<code>[</code>、<code>]</code>，我们是允许出现在 `url中的，不希望被 encode。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/get&#x27;</span>,</span><br><span class="line">  params: &#123;</span><br><span class="line">    foo: <span class="string">&#x27;@:$, &#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>最终请求的 <code>url</code> 是 <code>/base/get?foo=@:$+</code>，注意，我们会把空格 ``转换成 <code>+</code>。</p>
<h3 id="3-1-5-空值忽略"><a href="#3-1-5-空值忽略" class="headerlink" title="3.1.5 空值忽略"></a>3.1.5 空值忽略</h3><p>对于值为 <code>null</code> 或者 <code>undefined</code> 的属性，我们是不会添加到 url 参数中的。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/get&#x27;</span>,</span><br><span class="line">  params: &#123;</span><br><span class="line">    foo: <span class="string">&#x27;bar&#x27;</span>,</span><br><span class="line">    baz: <span class="literal">null</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>最终请求的 <code>url</code> 是 <code>/base/get?foo=bar</code>。</p>
<h3 id="3-1-6-丢弃-url-中的哈希标记"><a href="#3-1-6-丢弃-url-中的哈希标记" class="headerlink" title="3.1.6 丢弃 url 中的哈希标记"></a>3.1.6 丢弃 url 中的哈希标记</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/get#hash&#x27;</span>,</span><br><span class="line">  params: &#123;</span><br><span class="line">    foo: <span class="string">&#x27;bar&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>最终请求的 <code>url</code> 是 <code>/base/get?foo=bar</code></p>
<h3 id="3-1-7-保留-url-中已存在的参数"><a href="#3-1-7-保留-url-中已存在的参数" class="headerlink" title="3.1.7 保留 url 中已存在的参数"></a>3.1.7 保留 url 中已存在的参数</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/get?foo=bar&#x27;</span>,</span><br><span class="line">  params: &#123;</span><br><span class="line">    bar: <span class="string">&#x27;baz&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>最终请求的 <code>url</code> 是 <code>/base/get?foo=bar&amp;bar=baz</code></p>
<h2 id="3-2-buildURL-函数实现"><a href="#3-2-buildURL-函数实现" class="headerlink" title="3.2 buildURL 函数实现"></a>3.2 buildURL 函数实现</h2><p>根据我们之前的需求分析，我们要实现一个工具函数，把 <code>params</code> 拼接到 <code>url</code> 上。我们希望把项目中的一些工具函数、辅助方法独立管理，于是我们创建一个 <code>helpers</code> 目录，在这个目录下创建 <code>url.ts</code> 文件，未来会把处理 <code>url</code> 相关的工具函数都放在该文件中。</p>
<p><code>helpers/url.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; isDate, isObject &#125; <span class="keyword">from</span> <span class="string">&#x27;./util&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">encode</span> (<span class="params">val: <span class="built_in">string</span></span>): <span class="title">string</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">encodeURIComponent</span>(val)</span><br><span class="line">    .replace(<span class="regexp">/%40/g</span>, <span class="string">&#x27;@&#x27;</span>)</span><br><span class="line">    .replace(<span class="regexp">/%3A/gi</span>, <span class="string">&#x27;:&#x27;</span>)</span><br><span class="line">    .replace(<span class="regexp">/%24/g</span>, <span class="string">&#x27;$&#x27;</span>)</span><br><span class="line">    .replace(<span class="regexp">/%2C/gi</span>, <span class="string">&#x27;,&#x27;</span>)</span><br><span class="line">    .replace(<span class="regexp">/%20/g</span>, <span class="string">&#x27;+&#x27;</span>)</span><br><span class="line">    .replace(<span class="regexp">/%5B/gi</span>, <span class="string">&#x27;[&#x27;</span>)</span><br><span class="line">    .replace(<span class="regexp">/%5D/gi</span>, <span class="string">&#x27;]&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">bulidURL</span> (<span class="params">url: <span class="built_in">string</span>, params?: <span class="built_in">any</span></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (!params) &#123;</span><br><span class="line">    <span class="keyword">return</span> url</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> parts: <span class="built_in">string</span>[] = []</span><br><span class="line"></span><br><span class="line">  <span class="built_in">Object</span>.keys(params).forEach(<span class="function">(<span class="params">key</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> val = params[key]</span><br><span class="line">    <span class="keyword">if</span> (val === <span class="literal">null</span> || <span class="keyword">typeof</span> val === <span class="string">&#x27;undefined&#x27;</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">let</span> values: <span class="built_in">string</span>[]</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">Array</span>.isArray(val)) &#123;</span><br><span class="line">      values = val</span><br><span class="line">      key += <span class="string">&#x27;[]&#x27;</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      values = [val]</span><br><span class="line">    &#125;</span><br><span class="line">    values.forEach(<span class="function">(<span class="params">val</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (isDate(val)) &#123;</span><br><span class="line">        val = val.toISOString()</span><br><span class="line">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (isObject(val)) &#123;</span><br><span class="line">        val = <span class="built_in">JSON</span>.stringify(val)</span><br><span class="line">      &#125;</span><br><span class="line">      parts.push(<span class="string">`<span class="subst">$&#123;encode(key)&#125;</span>=<span class="subst">$&#123;encode(val)&#125;</span>`</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> serializedParams = parts.join(<span class="string">&#x27;&amp;&#x27;</span>)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (serializedParams) &#123;</span><br><span class="line">    <span class="keyword">const</span> markIndex = url.indexOf(<span class="string">&#x27;#&#x27;</span>)</span><br><span class="line">    <span class="keyword">if</span> (markIndex !== -<span class="number">1</span>) &#123;</span><br><span class="line">      url = url.slice(<span class="number">0</span>, markIndex)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    url += (url.indexOf(<span class="string">&#x27;?&#x27;</span>) === -<span class="number">1</span> ? <span class="string">&#x27;?&#x27;</span> : <span class="string">&#x27;&amp;&#x27;</span>) + serializedParams</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> url</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>helpers/util.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> toString = <span class="built_in">Object</span>.prototype.toString</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">isDate</span> (<span class="params">val: <span class="built_in">any</span></span>): <span class="title">val</span> <span class="title">is</span> <span class="title">Date</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> toString.call(val) === <span class="string">&#x27;[object Date]&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">isObject</span> (<span class="params">val: <span class="built_in">any</span></span>): <span class="title">val</span> <span class="title">is</span> <span class="title">Object</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> val !== <span class="literal">null</span> &amp;&amp; <span class="keyword">typeof</span> val === <span class="string">&#x27;object&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="3-3-实现-url-参数处理逻辑"><a href="#3-3-实现-url-参数处理逻辑" class="headerlink" title="3.3 实现 url 参数处理逻辑"></a>3.3 实现 url 参数处理逻辑</h2><p>我们已经实现了 <code>buildURL</code> 函数，接下来我们来利用它实现 <code>url</code> 参数的处理逻辑。</p>
<p>在 <code>index.ts</code> 文件中添加如下代码：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; buildURL &#125; <span class="keyword">from</span> <span class="string">&#x27;./helpers/url&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">axios</span> (<span class="params">config: AxiosRequestConfig</span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">  processConfig(config)</span><br><span class="line">  xhr(config)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">processConfig</span> (<span class="params">config: AxiosRequestConfig</span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">  config.url = transformUrl(config)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">transformUrl</span> (<span class="params">config: AxiosRequestConfig</span>): <span class="title">string</span> </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; url, params &#125; = config</span><br><span class="line">  <span class="keyword">return</span> buildURL(url, params)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在执行 <code>xhr</code> 函数前，我们先执行 <code>processConfig</code> 方法，对 <code>config</code> 中的数据做处理，除了对 <code>url</code> 和 <code>params</code> 处理之外，未来还会处理其它属性。</p>
<p>在 <code>processConfig</code> 函数内部，我们通过执行 <code>transformUrl</code> 函数修改了 <code>config.url</code>，该函数内部调用了 <code>buildURL</code>。</p>
<p>那么至此，我们对 <code>url</code> 参数处理逻辑就实现完了，接下来我们就开始编写 demo 了。</p>
<h2 id="3-4-demo-编写"><a href="#3-4-demo-编写" class="headerlink" title="3.4 demo 编写"></a>3.4 demo 编写</h2><p>在 <code>examples</code> 目录下创建 <code>base</code> 目录，在 <code>base</code> 目录下创建 <code>index.html</code>:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Base example<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;/__build__/base.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>接着创建 <code>app.ts</code> 作为入口文件：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;../../src/index&#x27;</span></span><br><span class="line"></span><br><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/get&#x27;</span>,</span><br><span class="line">  params: &#123;</span><br><span class="line">    foo: [<span class="string">&#x27;bar&#x27;</span>, <span class="string">&#x27;baz&#x27;</span>]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/get&#x27;</span>,</span><br><span class="line">  params: &#123;</span><br><span class="line">    foo: &#123;</span><br><span class="line">      bar: <span class="string">&#x27;baz&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> date = <span class="keyword">new</span> <span class="built_in">Date</span>()</span><br><span class="line"></span><br><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/get&#x27;</span>,</span><br><span class="line">  params: &#123;</span><br><span class="line">    date</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/get&#x27;</span>,</span><br><span class="line">  params: &#123;</span><br><span class="line">    foo: <span class="string">&#x27;@:$, &#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/get&#x27;</span>,</span><br><span class="line">  params: &#123;</span><br><span class="line">    foo: <span class="string">&#x27;bar&#x27;</span>,</span><br><span class="line">    baz: <span class="literal">null</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/get#hash&#x27;</span>,</span><br><span class="line">  params: &#123;</span><br><span class="line">    foo: <span class="string">&#x27;bar&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/get?foo=bar&#x27;</span>,</span><br><span class="line">  params: &#123;</span><br><span class="line">    bar: <span class="string">&#x27;baz&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>接着在 <code>server.js</code> 添加新的接口路由：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">router.get(<span class="string">&#x27;/base/get&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  res.json(req.query)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>然后在命令行运行 <code>npm run dev</code>，接着打开 chrome 浏览器，访问 <code>http://localhost:8080/</code> 即可访问我们的 demo 了，我们点到 <code>Base</code> 目录下，通过开发者工具的 network 部分我们可以看到成功发送的多条请求，并可以观察它们最终请求的 url，已经如期添加了请求参数。</p>
<p>那么至此我们的请求 <code>url</code> 参数处理编写完了，下一小节我们会对 <code>request body</code> 数据做处理。</p>
<h1 id="4-处理请求-body-数据"><a href="#4-处理请求-body-数据" class="headerlink" title="4.处理请求 body 数据"></a>4.处理请求 body 数据</h1><h2 id="4-1-需求分析"><a href="#4-1-需求分析" class="headerlink" title="4.1 需求分析"></a>4.1 需求分析</h2><p>我们通过执行 <code>XMLHttpRequest</code> 对象实例的 <code>send</code> 方法来发送请求，并通过该方法的参数设置请求 <code>body</code> 数据，我们可以去 <a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/send">MDN</a>查阅该方法支持的参数类型。</p>
<p>我们发现 <code>send</code> 方法的参数支持 <code>Document</code> 和 <code>BodyInit</code> 类型，<code>BodyInit</code> 包括了 <code>Blob</code>, <code>BufferSource</code>, <code>FormData</code>, <code>URLSearchParams</code>, <code>ReadableStream</code>、<code>USVString</code>，当没有数据的时候，我们还可以传入 <code>null</code>。</p>
<p>但是我们最常用的场景还是传一个普通对象给服务端，例如：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/post&#x27;</span>,</span><br><span class="line">  data: &#123; </span><br><span class="line">    a: <span class="number">1</span>,</span><br><span class="line">    b: <span class="number">2</span> </span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>这个时候 <code>data</code>是不能直接传给 <code>send</code> 函数的，我们需要把它转换成 JSON 字符串。</p>
<h2 id="4-2-transformRequest-函数实现"><a href="#4-2-transformRequest-函数实现" class="headerlink" title="4.2 transformRequest 函数实现"></a>4.2 transformRequest 函数实现</h2><p>根据需求分析，我们要实现一个工具函数，对 request 中的 <code>data</code> 做一层转换。我们在 <code>helpers</code> 目录新建 <code>data.ts</code> 文件。</p>
<p><code>helpers/data.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; isPlainObject &#125; <span class="keyword">from</span> <span class="string">&#x27;./util&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">transformRequest</span> (<span class="params">data: <span class="built_in">any</span></span>): <span class="title">any</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (isPlainObject(data)) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">JSON</span>.stringify(data)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> data</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>helpers/util.js</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">isPlainObject</span> (<span class="params">val: <span class="built_in">any</span></span>): <span class="title">val</span> <span class="title">is</span> <span class="title">Object</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> toString.call(val) === <span class="string">&#x27;[object Object]&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里为什么要使用 <code>isPlainObject</code> 函数判断，而不用之前的 <code>isObject</code> 函数呢，因为 <code>isObject</code> 的判断方式，对于 <code>FormData</code>、<code>ArrayBuffer</code> 这些类型，<code>isObject</code> 判断也为 <code>true</code>，但是这些类型的数据我们是不需要做处理的，而 <code>isPlainObject</code> 的判断方式，只有我们定义的普通 <code>JSON</code> 对象才能满足。</p>
<p><code>helpers/url.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (isDate(val)) &#123;</span><br><span class="line">  val = val.toISOString()</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (isPlainObject(val)) &#123;</span><br><span class="line">  val = <span class="built_in">JSON</span>.stringify(val)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>对于上节课我们对请求参数值的判断，我们也应该用 <code>isPlainObject</code> 才更加合理。</p>
<p><code>helpers/util.js</code></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; export function isObject (val: any): val is Object &#123;</span><br><span class="line">&#x2F;&#x2F;   return val !&#x3D;&#x3D; null &amp;&amp; typeof val &#x3D;&#x3D;&#x3D; &#39;object&#39;</span><br><span class="line">&#x2F;&#x2F; &#125;</span><br></pre></td></tr></table></figure>

<p>既然现在 <code>isObject</code> 方法不再使用，我们先将其注释。</p>
<h2 id="4-3-实现请求-body-处理逻辑"><a href="#4-3-实现请求-body-处理逻辑" class="headerlink" title="4.3 实现请求 body 处理逻辑"></a>4.3 实现请求 body 处理逻辑</h2><p><code>index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; transformRequest &#125; <span class="keyword">from</span> <span class="string">&#x27;./helpers/data&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">processConfig</span> (<span class="params">config: AxiosRequestConfig</span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">  config.url = transformURL(config)</span><br><span class="line">  config.data = transformRequestData(config)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">transformRequestData</span> (<span class="params">config: AxiosRequestConfig</span>): <span class="title">any</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> transformRequest(config.data)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们定义了 <code>transformRequestData</code> 函数，去转换请求 <code>body</code> 的数据，内部调用了我们刚刚实现的的 <code>transformRequest</code> 方法。</p>
<p>然后我们在 <code>processConfig</code> 内部添加了这段逻辑，在处理完 url 后接着对 <code>config</code> 中的 <code>data</code> 做处理。</p>
<h2 id="4-4-编写-demo"><a href="#4-4-编写-demo" class="headerlink" title="4.4 编写 demo"></a>4.4 编写 demo</h2><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/post&#x27;</span>,</span><br><span class="line">  data: &#123;</span><br><span class="line">    a: <span class="number">1</span>,</span><br><span class="line">    b: <span class="number">2</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> arr = <span class="keyword">new</span> <span class="built_in">Int32Array</span>([<span class="number">21</span>, <span class="number">31</span>])</span><br><span class="line"></span><br><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/buffer&#x27;</span>,</span><br><span class="line">  data: arr</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>我们在 <code>examples/base/app.ts</code> 添加 2 段代码，第一个 post 请求的 <code>data</code> 是一个普通对象，第二个请求的 <code>data</code> 是一个 <code>Int32Array</code> 类型的数据，它是可以直接传给 <code>XMLHttpRequest</code> 对象的 <code>send</code> 方法的。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">router.post(<span class="string">&#x27;/base/post&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  res.json(req.body)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">router.post(<span class="string">&#x27;/base/buffer&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">let</span> msg = []</span><br><span class="line">  req.on(<span class="string">&#x27;data&#x27;</span>, <span class="function">(<span class="params">chunk</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (chunk) &#123;</span><br><span class="line">      msg.push(chunk)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line">  req.on(<span class="string">&#x27;end&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> buf = Buffer.concat(msg)</span><br><span class="line">    res.json(buf.toJSON())</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>我们接着在 <code>examples/server.js</code> 中添加 2 个路由，分别针对这俩种请求，返回请求传入的数据。</p>
<p>然后我们打开浏览器运行 demo，看一下结果，我们发现 <code>/base/buffer</code> 的请求是可以拿到数据，但是 <code>base/post</code> 请求的 response 里却返回的是一个空对象，这是什么原因呢？</p>
<p>实际上是因为我们虽然执行 <code>send</code> 方法的时候把普通对象 <code>data</code> 转换成一个 <code>JSON</code> 字符串，但是我们请求<code>header</code> 的 <code>Content-Type</code> 是 <code>text/plain;charset=UTF-8</code>，导致了服务端接受到请求并不能正确解析请求 <code>body</code> 的数据。</p>
<p>知道这个问题后，下面一节课我们来实现对请求 <code>header</code> 的处理。</p>
<h1 id="5-处理请求-header"><a href="#5-处理请求-header" class="headerlink" title="5.处理请求 header"></a>5.处理请求 header</h1><h2 id="5-1-需求分析"><a href="#5-1-需求分析" class="headerlink" title="5.1 需求分析"></a>5.1 需求分析</h2><p>我们上节课遗留了一个问题：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/post&#x27;</span>,</span><br><span class="line">  data: &#123;</span><br><span class="line">    a: <span class="number">1</span>,</span><br><span class="line">    b: <span class="number">2</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>我们做了请求数据的处理，把 <code>data</code> 转换成了 JSON 字符串，但是数据发送到服务端的时候，服务端并不能正常解析我们发送的数据，因为我们并没有给请求 <code>header</code> 设置正确的 <code>Content-Type</code>。</p>
<p>所以首先我们要支持发送请求的时候，可以支持配置 <code>headers</code> 属性，如下：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/post&#x27;</span>,</span><br><span class="line">  headers: &#123;</span><br><span class="line">    <span class="string">&#x27;content-type&#x27;</span>: <span class="string">&#x27;application/json;charset=utf-8&#x27;</span></span><br><span class="line">  &#125;,</span><br><span class="line">  data: &#123;</span><br><span class="line">    a: <span class="number">1</span>,</span><br><span class="line">    b: <span class="number">2</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<h2 id="5-2-processHeaders-函数实现"><a href="#5-2-processHeaders-函数实现" class="headerlink" title="5.2 processHeaders 函数实现"></a>5.2 processHeaders 函数实现</h2><p>根据需求分析，我们要实现一个工具函数，对 request 中的 <code>headers</code> 做一层加工。我们在 <code>helpers</code> 目录新建 <code>headers.ts</code> 文件。</p>
<p><code>helpers/headers.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; isPlainObject &#125; <span class="keyword">from</span> <span class="string">&#x27;./util&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">normalizeHeaderName</span> (<span class="params">headers: <span class="built_in">any</span>, normalizedName: <span class="built_in">string</span></span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (!headers) &#123;</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="built_in">Object</span>.keys(headers).forEach(<span class="function"><span class="params">name</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (name !== normalizedName &amp;&amp; name.toUpperCase() === normalizedName.toUpperCase()) &#123;</span><br><span class="line">      headers[normalizedName] = headers[name]</span><br><span class="line">      <span class="keyword">delete</span> headers[name]</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">processHeaders</span> (<span class="params">headers: <span class="built_in">any</span>, data: <span class="built_in">any</span></span>): <span class="title">any</span> </span>&#123;</span><br><span class="line">  normalizeHeaderName(headers, <span class="string">&#x27;Content-Type&#x27;</span>)</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">if</span> (isPlainObject(data)) &#123;</span><br><span class="line">    <span class="keyword">if</span> (headers &amp;&amp; !headers[<span class="string">&#x27;Content-Type&#x27;</span>]) &#123;</span><br><span class="line">      headers[<span class="string">&#x27;Content-Type&#x27;</span>] = <span class="string">&#x27;application/json;charset=utf-8&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> headers</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里有个需要注意的点，因为请求 <code>header</code> 属性是大小写不敏感的，比如我们之前的例子传入 <code>header</code> 的属性名 <code>content-type</code> 就是全小写的，所以我们先要把一些 <code>header</code> 属性名规范化。</p>
<h2 id="5-3-实现请求-header-处理逻辑"><a href="#5-3-实现请求-header-处理逻辑" class="headerlink" title="5.3 实现请求 header 处理逻辑"></a>5.3 实现请求 header 处理逻辑</h2><p>在这之前，我们先修改一下 <code>AxiosRequestConfig</code> 接口类型的定义，添加 <code>headers</code> 这个可选属性：</p>
<p><code>types/index.ts</code>：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">export interface AxiosRequestConfig &#123;</span><br><span class="line">  url: string</span><br><span class="line">  method?: Method</span><br><span class="line">  data?: any</span><br><span class="line">  params?: any</span><br><span class="line">  headers?: any</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">processConfig</span> (<span class="params">config: AxiosRequestConfig</span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">  config.url = transformURL(config)</span><br><span class="line">  config.headers = transformHeaders(config)</span><br><span class="line">  config.data = transformRequestData(config)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">transformHeaders</span> (<span class="params">config: AxiosRequestConfig</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; headers = &#123;&#125;, data &#125; = config</span><br><span class="line">  <span class="keyword">return</span> processHeaders(headers, data)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>因为我们处理 <code>header</code> 的时候依赖了 <code>data</code>，所以要在处理请求 <code>body</code> 数据之前处理请求 <code>header</code>。</p>
<p><code>xhr.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="function"><span class="keyword">function</span> <span class="title">xhr</span> (<span class="params">config: AxiosRequestConfig</span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; data = <span class="literal">null</span>, url, method = <span class="string">&#x27;get&#x27;</span>, headers &#125; = config</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> request = <span class="keyword">new</span> XMLHttpRequest()</span><br><span class="line"></span><br><span class="line">  request.open(method.toUpperCase(), url, <span class="literal">true</span>)</span><br><span class="line"></span><br><span class="line">  <span class="built_in">Object</span>.keys(headers).forEach(<span class="function">(<span class="params">name</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (data === <span class="literal">null</span> &amp;&amp; name.toLowerCase() === <span class="string">&#x27;content-type&#x27;</span>) &#123;</span><br><span class="line">      <span class="keyword">delete</span> headers[name]</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      request.setRequestHeader(name, headers[name])</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  request.send(data)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里要额外判断一个逻辑，当我们传入的 <code>data</code> 为空的时候，请求 <code>header</code> 配置 <code>Content-Type</code> 是没有意义的，于是我们把它删除。</p>
<h2 id="5-4-demo-编写"><a href="#5-4-demo-编写" class="headerlink" title="5.4 demo 编写"></a>5.4 demo 编写</h2><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/post&#x27;</span>,</span><br><span class="line">  data: &#123;</span><br><span class="line">    a: <span class="number">1</span>,</span><br><span class="line">    b: <span class="number">2</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/post&#x27;</span>,</span><br><span class="line">  headers: &#123;</span><br><span class="line">    <span class="string">&#x27;content-type&#x27;</span>: <span class="string">&#x27;application/json;&#x27;</span></span><br><span class="line">  &#125;,</span><br><span class="line">  data: &#123;</span><br><span class="line">    a: <span class="number">1</span>,</span><br><span class="line">    b: <span class="number">2</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> paramsString = <span class="string">&#x27;q=URLUtils.searchParams&amp;topic=api&#x27;</span></span><br><span class="line"><span class="keyword">const</span> searchParams = <span class="keyword">new</span> URLSearchParams(paramsString)</span><br><span class="line"></span><br><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/post&#x27;</span>,</span><br><span class="line">  data: searchParams</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>通过 demo 我们可以看到，当我们请求的数据是普通对象并且没有配置 <code>headers</code> 的时候，会自动为其添加 <code>Content-Type:application/json;charset=utf-8</code>；同时我们发现当 data 是某些类型如 <code>URLSearchParams</code> 的时候，浏览器会自动为请求 <code>header</code>加上合适的 <code>Content-Type</code>。</p>
<p>至此我们对于请求的处理逻辑暂时告一段落。目前我们的请求从网络层面是可以收到服务端的响应的，下一节课我们就从代码层面来处理服务端响应，并且让调用方可以拿到从服务端返回的数据。</p>
<h1 id="6-获取响应数据"><a href="#6-获取响应数据" class="headerlink" title="6.获取响应数据"></a>6.获取响应数据</h1><h2 id="6-1-需求分析"><a href="#6-1-需求分析" class="headerlink" title="6.1 需求分析"></a>6.1 需求分析</h2><p>在前面的章节中，我们发送的请求都可以从网络层面接收到服务端返回的数据，但是代码层面并没有做任何关于返回数据的处理。我们希望能处理服务端响应的数据，并支持 Promise 链式调用的方式，如下：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/post&#x27;</span>,</span><br><span class="line">  data: &#123;</span><br><span class="line">    a: <span class="number">1</span>,</span><br><span class="line">    b: <span class="number">2</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;).then(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>我们可以拿到 <code>res</code> 对象，并且我们希望该对象包括：服务端返回的数据 <code>data</code>，HTTP 状态码<code>status</code>，状态消息 <code>statusText</code>，响应头 <code>headers</code>、请求配置对象 <code>config</code> 以及请求的 <code>XMLHttpRequest</code> 对象实例 <code>request</code>。</p>
<h2 id="6-2-定义接口类型"><a href="#6-2-定义接口类型" class="headerlink" title="6.2 定义接口类型"></a>6.2 定义接口类型</h2><p>根据需求，我们可以定义一个 <code>AxiosResponse</code> 接口类型：</p>
<p><code>types/index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosResponse &#123;</span><br><span class="line">  data: <span class="built_in">any</span></span><br><span class="line">  status: <span class="built_in">number</span></span><br><span class="line">  statusText: <span class="built_in">string</span></span><br><span class="line">  headers: <span class="built_in">any</span></span><br><span class="line">  config: AxiosRequestConfig</span><br><span class="line">  request: <span class="built_in">any</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>另外，<code>axios</code> 函数返回的是一个 <code>Promise</code> 对象，我们可以定义一个 <code>AxiosPromise</code> 接口，它继承于 <code>Promise&lt;AxiosResponse&gt;</code> 这个泛型接口：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosPromise <span class="keyword">extends</span> Promise&lt;AxiosResponse&gt; &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>另外，<code>axios</code> 函数返回的是一个 <code>Promise</code> 对象，我们可以定义一个 <code>AxiosPromise</code> 接口，它继承于 <code>Promise&lt;AxiosResponse&gt;</code> 这个泛型接口：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosPromise <span class="keyword">extends</span> Promise&lt;AxiosResponse&gt; &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这样的话，当 <code>axios</code> 返回的是 <code>AxiosPromise</code> 类型，那么 <code>resolve</code> 函数中的参数就是一个 <code>AxiosResponse</code> 类型。</p>
<p>对于一个 AJAX 请求的 <code>response</code>，我们是可以指定它的响应的数据类型的，通过设置 <code>XMLHttpRequest</code> 对象的 <a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType"><code>responseType</code></a></p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosRequestConfig &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  responseType?: XMLHttpRequestResponseType</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>responseType</code> 的类型是一个 <code>XMLHttpRequestResponseType</code> 类型，它的定义是 <code>&quot;&quot; | &quot;arraybuffer&quot; | &quot;blob&quot; | &quot;document&quot; | &quot;json&quot; | &quot;text&quot;</code> 字符串字面量类型。</p>
<h2 id="6-3-实现获取响应数据逻辑"><a href="#6-3-实现获取响应数据逻辑" class="headerlink" title="6.3 实现获取响应数据逻辑"></a>6.3 实现获取响应数据逻辑</h2><p>首先我们要在 <code>xhr</code> 函数添加 <a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/onreadystatechange"><code>onreadystatechange</code></a>事件处理函数，并且让 <code>xhr</code> 函数返回的是 AxiosPromise 类型。</p>
<p><code>xhr.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="function"><span class="keyword">function</span> <span class="title">xhr</span>(<span class="params">config: AxiosRequestConfig</span>): <span class="title">AxiosPromise</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; data = <span class="literal">null</span>, url, method = <span class="string">&#x27;get&#x27;</span>, headers, responseType &#125; = config</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> request = <span class="keyword">new</span> XMLHttpRequest()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (responseType) &#123;</span><br><span class="line">      request.responseType = responseType</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    request.open(method.toUpperCase(), url, <span class="literal">true</span>)</span><br><span class="line"></span><br><span class="line">    request.onreadystatechange = <span class="function"><span class="keyword">function</span> <span class="title">handleLoad</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">      <span class="keyword">if</span> (request.readyState !== <span class="number">4</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">const</span> responseHeaders = request.getAllResponseHeaders()</span><br><span class="line">      <span class="keyword">const</span> responseData = responseType &amp;&amp; responseType !== <span class="string">&#x27;text&#x27;</span> ? request.response : request.responseText</span><br><span class="line">      <span class="keyword">const</span> response: AxiosResponse = &#123;</span><br><span class="line">        data: responseData,</span><br><span class="line">        status: request.status,</span><br><span class="line">        statusText: request.statusText,</span><br><span class="line">        headers: responseHeaders,</span><br><span class="line">        config,</span><br><span class="line">        request</span><br><span class="line">      &#125;</span><br><span class="line">      resolve(response)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">Object</span>.keys(headers).forEach(<span class="function">(<span class="params">name</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (data === <span class="literal">null</span> &amp;&amp; name.toLowerCase() === <span class="string">&#x27;content-type&#x27;</span>) &#123;</span><br><span class="line">        <span class="keyword">delete</span> headers[name]</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        request.setRequestHeader(name, headers[name])</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    request.send(data)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>注意，我们这里还判断了如果 <code>config</code> 中配置了 <code>responseType</code>，我们把它设置到 <code>request.responseType</code> 中。在 <code>onreadystatechange</code> 事件函数中，我们构造了 <code>AxiosResponse</code> 类型的 <code>reponse</code> 对象，并把它 <code>resolve</code> 出去。</p>
<p>修改了 <code>xhr</code> 函数，我们同样也要对应修改 <code>axios</code> 函数：</p>
<p><code>index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">axios</span>(<span class="params">config: AxiosRequestConfig</span>): <span class="title">AxiosPromise</span> </span>&#123;</span><br><span class="line">  processConfig(config)</span><br><span class="line">  <span class="keyword">return</span> xhr(config)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这样我们就实现了 <code>axios</code> 函数的 Promise 化。</p>
<h2 id="6-4-demo-编写"><a href="#6-4-demo-编写" class="headerlink" title="6.4 demo 编写"></a>6.4 demo 编写</h2><p>我们在 <code>examples/base/app.ts</code> 文件中添加 2 段代码：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/post&#x27;</span>,</span><br><span class="line">  data: &#123;</span><br><span class="line">    a: <span class="number">1</span>,</span><br><span class="line">    b: <span class="number">2</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;).then(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/base/post&#x27;</span>,</span><br><span class="line">  responseType: <span class="string">&#x27;json&#x27;</span>,</span><br><span class="line">  data: &#123;</span><br><span class="line">    a: <span class="number">3</span>,</span><br><span class="line">    b: <span class="number">4</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;).then(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>我们打开浏览器运行 demo，看一下结果，发现我们可以正常 log 出这个 <code>res</code> 变量，它包含 <code>AxiosResponse</code> 类型中定义的那些属性，不过我们发现 2 个小问题：第一个是 <code>headers</code> 属性是一个字符串，我们需要把它解析成对象类型；第二个是在第一个请求中，得到的数据是一个 JSON 字符串，我们也需要把它转换成对象类型。</p>
<p>那么下一小节，我们将来解决第一个问题，对于响应的 <code>header</code> 做处理。</p>
<h1 id="7-处理响应-header"><a href="#7-处理响应-header" class="headerlink" title="7.处理响应 header"></a>7.处理响应 header</h1><h2 id="7-1-需求分析"><a href="#7-1-需求分析" class="headerlink" title="7.1 需求分析"></a>7.1 需求分析</h2><p>我们通过 <code>XMLHttpRequest</code> 对象的 <code>getAllResponseHeaders</code> 方法获取到的值是如下一段字符串：</p>
<figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">date: Fri, 05 Apr 2019 12:40:49 GMT</span><br><span class="line">etag: W/&quot;d-Ssxx4FRxEutDLwo2+xkkxKc4y0k&quot;</span><br><span class="line">connection: keep-alive</span><br><span class="line">x-powered-by: Express</span><br><span class="line">content-length: 13</span><br><span class="line">content-type: application/json; charset=utf-8</span><br></pre></td></tr></table></figure>

<p>每一行都是以回车符和换行符 <code>\r\n</code> 结束，它们是每个 <code>header</code> 属性的分隔符。对于上面这串字符串，我们希望最终解析成一个对象结构：</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  date: &#x27;Fri, 05 Apr 2019 12:40:49 GMT&#x27;</span><br><span class="line">  etag: &#x27;W/&quot;d-Ssxx4FRxEutDLwo2+xkkxKc4y0k&quot;&#x27;,</span><br><span class="line">  connection: &#x27;keep-alive&#x27;,</span><br><span class="line">  &#x27;x-powered-by&#x27;: &#x27;Express&#x27;,</span><br><span class="line">  &#x27;content-length&#x27;: &#x27;13&#x27;</span><br><span class="line">  &#x27;content-type&#x27;: &#x27;application/json; charset=utf-8&#x27;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="7-2-parseHeaders-函数实现及应用"><a href="#7-2-parseHeaders-函数实现及应用" class="headerlink" title="7.2 parseHeaders 函数实现及应用"></a>7.2 parseHeaders 函数实现及应用</h2><p>根据需求分析，我们要实现一个 <code>parseHeaders</code> 工具函数。</p>
<p><code>helpers/headers.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">parseHeaders</span>(<span class="params">headers: <span class="built_in">string</span></span>): <span class="title">any</span> </span>&#123;</span><br><span class="line">  <span class="keyword">let</span> parsed = <span class="built_in">Object</span>.create(<span class="literal">null</span>)</span><br><span class="line">  <span class="keyword">if</span> (!headers) &#123;</span><br><span class="line">    <span class="keyword">return</span> parsed</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  headers.split(<span class="string">&#x27;\r\n&#x27;</span>).forEach(<span class="function"><span class="params">line</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> [key, val] = line.split(<span class="string">&#x27;:&#x27;</span>)</span><br><span class="line">    key = key.trim().toLowerCase()</span><br><span class="line">    <span class="keyword">if</span> (!key) &#123;</span><br><span class="line">      <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (val) &#123;</span><br><span class="line">      val = val.trim()</span><br><span class="line">    &#125;</span><br><span class="line">    parsed[key] = val</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> parsed</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>然后我们使用这个工具函数：</p>
<p><code>xhr.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> responseHeaders = parseHeaders(request.getAllResponseHeaders())</span><br></pre></td></tr></table></figure>

<p>接着我们再去看刚才的 demo，发现我们已经把响应的 <code>headers</code> 字段从字符串解析成对象结构了。那么接下来，我们在解决之前遗留的第二个问题：对响应 <code>data</code> 字段的处理。</p>
<h1 id="8-处理响应-data"><a href="#8-处理响应-data" class="headerlink" title="8.处理响应 data"></a>8.处理响应 data</h1><h2 id="8-1-需求分析"><a href="#8-1-需求分析" class="headerlink" title="8.1 需求分析"></a>8.1 需求分析</h2><p>在我们不去设置 <code>responseType</code> 的情况下，当服务端返回给我们的数据是字符串类型，我们可以尝试去把它转换成一个 JSON 对象。例如：</p>
<figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">data: &quot;&#123;&quot;a&quot;:1,&quot;b&quot;:2&#125;&quot;</span><br></pre></td></tr></table></figure>

<p>我们把它转换成：</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">data: &#123;</span><br><span class="line">  a: 1,</span><br><span class="line">  b: 2</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="8-2-transformResponse-函数实现及应用"><a href="#8-2-transformResponse-函数实现及应用" class="headerlink" title="8.2 transformResponse 函数实现及应用"></a>8.2 transformResponse 函数实现及应用</h2><p>根据需求分析，我们要实现一个 <code>transformResponse</code> 工具函数。</p>
<p><code>helpers/data.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">transformResponse</span>(<span class="params">data: <span class="built_in">any</span></span>): <span class="title">any</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">typeof</span> data === <span class="string">&#x27;string&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      data = <span class="built_in">JSON</span>.parse(data)</span><br><span class="line">    &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">      <span class="comment">// do nothing</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> data</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">axios</span>(<span class="params">config: AxiosRequestConfig</span>): <span class="title">AxiosPromise</span> </span>&#123;</span><br><span class="line">  processConfig(config)</span><br><span class="line">  <span class="keyword">return</span> xhr(config).then(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> transformResponseData(res)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">transformResponseData</span>(<span class="params">res: AxiosResponse</span>): <span class="title">AxiosResponse</span> </span>&#123;</span><br><span class="line">  res.data = transformResponse(res.data)</span><br><span class="line">  <span class="keyword">return</span> res</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>接着我们再去看刚才的 demo，发现我们已经把响应的 <code>data</code> 字段从字符串解析成 JSON 对象结构了。</p>
<p>那么至此，我们的 <code>ts-axios</code> 的基础功能已经实现完毕。不过到目前为止，我们都仅仅实现的是正常情况的逻辑，下面一章我们要处理各种异常情况的逻辑。</p>
<h1 id="9-错误处理"><a href="#9-错误处理" class="headerlink" title="9.错误处理"></a>9.错误处理</h1><h2 id="9-1-需求分析"><a href="#9-1-需求分析" class="headerlink" title="9.1 需求分析"></a>9.1 需求分析</h2><p>在上一章节，我们实现了 <code>ts-axios</code> 的基础功能，但目前为止我们都是处理了正常接收请求的逻辑，并没有考虑到任何错误情况的处理，这对于一个程序的健壮性而言是远不够的，因此我们这一章需要对 AJAX 各种错误情况做处理。</p>
<p>并且我们希望程序也能捕获到这些错误，做进一步的处理。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/error/get&#x27;</span></span><br><span class="line">&#125;).then(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res)</span><br><span class="line">&#125;).catch(<span class="function">(<span class="params">e</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(e)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>如果在请求的过程中发生任何错误，我们都可以在 <code>reject</code> 回调函数中捕获到。</p>
<p>我们把错误分成了几类，接下来我们就来分别处理这些错误情况。</p>
<h2 id="9-2-处理网络异常错误"><a href="#9-2-处理网络异常错误" class="headerlink" title="9.2 处理网络异常错误"></a>9.2 处理网络异常错误</h2><p>当网络出现异常（比如不通）的时候发送请求会触发 <code>XMLHttpRequest</code> 对象实例的 <code>error</code> 事件，于是我们可以在 <a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequestEventTarget/onerror"><code>onerror</code></a>的事件回调函数中捕获此类错误。</p>
<p>我们在 <code>xhr</code> 函数中添加如下代码：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">request.onerror = <span class="function"><span class="keyword">function</span> <span class="title">handleError</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  reject(<span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">&#x27;Network Error&#x27;</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="9-3-处理超时错误"><a href="#9-3-处理超时错误" class="headerlink" title="9.3 处理超时错误"></a>9.3 处理超时错误</h2><p>我们可以设置某个请求的超时时间 <a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/timeout"><code>timeout</code></a>，也就是当请求发送后超过某个时间后仍然没收到响应，则请求自动终止，并触发 <code>timeout</code> 事件。</p>
<p>请求默认的超时时间是 0，即永不超时。所以我们首先需要允许程序可以配置超时时间：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosRequestConfig &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  timeout?: <span class="built_in">number</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>接着在 <code>xhr</code> 函数中添加如下代码：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; <span class="comment">/*...*/</span> timeout &#125; = config</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (timeout) &#123;</span><br><span class="line">  request.timeout = timeout</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">request.ontimeout = <span class="function"><span class="keyword">function</span> <span class="title">handleTimeout</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  reject(<span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">`Timeout of <span class="subst">$&#123;timeout&#125;</span> ms exceeded`</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="9-4-处理非-200-状态码"><a href="#9-4-处理非-200-状态码" class="headerlink" title="9.4 处理非 200 状态码"></a>9.4 处理非 200 状态码</h2><p>对于一个正常的请求，往往会返回 200-300 之间的 HTTP 状态码，对于不在这个区间的状态码，我们也把它们认为是一种错误的情况做处理。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">request.onreadystatechange = <span class="function"><span class="keyword">function</span> <span class="title">handleLoad</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (request.readyState !== <span class="number">4</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (request.status === <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> responseHeaders = parseHeaders(request.getAllResponseHeaders())</span><br><span class="line">  <span class="keyword">const</span> responseData =</span><br><span class="line">    responseType &amp;&amp; responseType !== <span class="string">&#x27;text&#x27;</span> ? request.response : request.responseText</span><br><span class="line">  <span class="keyword">const</span> response: AxiosResponse = &#123;</span><br><span class="line">    data: responseData,</span><br><span class="line">    status: request.status,</span><br><span class="line">    statusText: request.statusText,</span><br><span class="line">    headers: responseHeaders,</span><br><span class="line">    config,</span><br><span class="line">    request</span><br><span class="line">  &#125;</span><br><span class="line">  handleResponse(response)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">handleResponse</span>(<span class="params">response: AxiosResponse</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (response.status &gt;= <span class="number">200</span> &amp;&amp; response.status &lt; <span class="number">300</span>) &#123;</span><br><span class="line">    resolve(response)</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    reject(<span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">`Request failed with status code <span class="subst">$&#123;response.status&#125;</span>`</span>))</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们在 <code>onreadystatechange</code> 的回调函数中，添加了对 <a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/status"><code>request.status</code></a> 的判断，因为当出现网络错误或者超时错误的时候，该值都为 0。</p>
<p>接着我们在 <code>handleResponse</code> 函数中对 <code>request.status</code> 的值再次判断，如果是 <code>2xx</code> 的状态码，则认为是一个正常的请求，否则抛错。</p>
<h2 id="9-5-demo-编写"><a href="#9-5-demo-编写" class="headerlink" title="9.5 demo 编写"></a>9.5 demo 编写</h2><p>在 <code>examples</code> 目录下创建 <code>error</code> 目录，在 <code>error</code> 目录下创建 <code>index.html</code>:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Error example<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;/__build__/error.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>接着创建 <code>app.ts</code> 作为入口文件：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;../../src/index&#x27;</span></span><br><span class="line"></span><br><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/error/get1&#x27;</span></span><br><span class="line">&#125;).then(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res)</span><br><span class="line">&#125;).catch(<span class="function">(<span class="params">e</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(e)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/error/get&#x27;</span></span><br><span class="line">&#125;).then(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res)</span><br><span class="line">&#125;).catch(<span class="function">(<span class="params">e</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(e)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  axios(&#123;</span><br><span class="line">    method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">    url: <span class="string">&#x27;/error/get&#x27;</span></span><br><span class="line">  &#125;).then(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(res)</span><br><span class="line">  &#125;).catch(<span class="function">(<span class="params">e</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(e)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;, <span class="number">5000</span>)</span><br><span class="line"></span><br><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/error/timeout&#x27;</span>,</span><br><span class="line">  timeout: <span class="number">2000</span></span><br><span class="line">&#125;).then(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res)</span><br><span class="line">&#125;).catch(<span class="function">(<span class="params">e</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(e.message)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>接着在 <code>server.js</code> 添加新的接口路由：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">router.get(<span class="string">&#x27;/error/get&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="built_in">Math</span>.random() &gt; <span class="number">0.5</span>) &#123;</span><br><span class="line">    res.json(&#123;</span><br><span class="line">      msg: <span class="string">`hello world`</span></span><br><span class="line">    &#125;)</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    res.status(<span class="number">500</span>)</span><br><span class="line">    res.end()</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">router.get(<span class="string">&#x27;/error/timeout&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    res.json(&#123;</span><br><span class="line">      msg: <span class="string">`hello world`</span></span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;, <span class="number">3000</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>



<h1 id="10-错误信息增强"><a href="#10-错误信息增强" class="headerlink" title="10.错误信息增强"></a>10.错误信息增强</h1><h2 id="10-1-需求分析"><a href="#10-1-需求分析" class="headerlink" title="10.1 需求分析"></a>10.1 需求分析</h2><p>上一节课我们已经捕获了几类 AJAX 的错误，但是对于错误信息提供的非常有限，我们希望对外提供的信息不仅仅包含错误文本信息，还包括了请求对象配置 <code>config</code>，错误代码 <code>code</code>，<code>XMLHttpRequest</code> 对象实例 <code>request</code>以及自定义响应对象 <code>response</code>。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/error/timeout&#x27;</span>,</span><br><span class="line">  timeout: <span class="number">2000</span></span><br><span class="line">&#125;).then(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res)</span><br><span class="line">&#125;).catch(<span class="function">(<span class="params">e: AxiosError</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(e.message)</span><br><span class="line">  <span class="built_in">console</span>.log(e.request)</span><br><span class="line">  <span class="built_in">console</span>.log(e.code)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>这样对于应用方来说，他们就可以捕获到这些错误的详细信息，做进一步的处理。</p>
<p>那么接下来，我们就来对错误信息做增强。</p>
<h2 id="10-2-创建-AxiosError-类"><a href="#10-2-创建-AxiosError-类" class="headerlink" title="10.2 创建 AxiosError 类"></a>10.2 创建 AxiosError 类</h2><p>我们先来定义 <code>AxiosError</code> 类型接口，用于外部使用。</p>
<p><code>types/index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosError <span class="keyword">extends</span> Error &#123;</span><br><span class="line">  config: AxiosRequestConfig</span><br><span class="line">  code?: <span class="built_in">string</span></span><br><span class="line">  request?: <span class="built_in">any</span></span><br><span class="line">  response?: AxiosResponse</span><br><span class="line">  isAxiosError: <span class="built_in">boolean</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>接着我们创建 <code>error.ts</code> 文件，然后实现 <code>AxiosError</code> 类，它是继承于 <code>Error</code> 类。</p>
<p><code>helpers/error.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; AxiosRequestConfig, AxiosResponse &#125; <span class="keyword">from</span> <span class="string">&#x27;../types&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="class"><span class="keyword">class</span> <span class="title">AxiosError</span> <span class="keyword">extends</span> <span class="title">Error</span> </span>&#123;</span><br><span class="line">  isAxiosError: <span class="built_in">boolean</span></span><br><span class="line">  config: AxiosRequestConfig</span><br><span class="line">  code?: <span class="built_in">string</span> | <span class="literal">null</span></span><br><span class="line">  request?: <span class="built_in">any</span></span><br><span class="line">  response?: AxiosResponse</span><br><span class="line"></span><br><span class="line">  <span class="title">constructor</span>(<span class="params"></span></span><br><span class="line"><span class="params">    message: <span class="built_in">string</span>,</span></span><br><span class="line"><span class="params">    config: AxiosRequestConfig,</span></span><br><span class="line"><span class="params">    code?: <span class="built_in">string</span> | <span class="literal">null</span>,</span></span><br><span class="line"><span class="params">    request?: <span class="built_in">any</span>,</span></span><br><span class="line"><span class="params">    response?: AxiosResponse</span></span><br><span class="line"><span class="params">  </span>) &#123;</span><br><span class="line">    <span class="built_in">super</span>(message)</span><br><span class="line"></span><br><span class="line">    <span class="built_in">this</span>.config = config</span><br><span class="line">    <span class="built_in">this</span>.code = code</span><br><span class="line">    <span class="built_in">this</span>.request = request</span><br><span class="line">    <span class="built_in">this</span>.response = response</span><br><span class="line">    <span class="built_in">this</span>.isAxiosError = <span class="literal">true</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">Object</span>.setPrototypeOf(<span class="built_in">this</span>, AxiosError.prototype)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">createError</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  message: <span class="built_in">string</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">  config: AxiosRequestConfig,</span></span></span><br><span class="line"><span class="function"><span class="params">  code?: <span class="built_in">string</span> | <span class="literal">null</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">  request?: <span class="built_in">any</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">  response?: AxiosResponse</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">AxiosError</span> </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> error = <span class="keyword">new</span> AxiosError(message, config, code, request, response)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> error</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>AxiosError</code> 继承于 <code>Error</code> 类，添加了一些自己的属性：<code>config</code>、<code>code</code>、<code>request</code>、<code>response</code>、<code>isAxiosError</code> 等属性。这里要注意一点，我们使用了 <code>Object.setPrototypeOf(this, AxiosError.prototype)</code>，这段代码的目的是为了解决 TypeScript 继承一些内置对象的时候的坑，<a target="_blank" rel="noopener" href="https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work">参考</a>。</p>
<p>另外，为了方便使用，我们对外暴露了一个 <code>createError</code> 的工厂方法。</p>
<h2 id="10-3-createError-方法应用"><a href="#10-3-createError-方法应用" class="headerlink" title="10.3 createError 方法应用"></a>10.3 createError 方法应用</h2><p>修改关于错误对象创建部分的逻辑，如下：</p>
<p><code>xhr.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; createError &#125; <span class="keyword">from</span> <span class="string">&#x27;./helpers/error&#x27;</span></span><br><span class="line"></span><br><span class="line">request.onerror = <span class="function"><span class="keyword">function</span> <span class="title">handleError</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  reject(createError(</span><br><span class="line">    <span class="string">&#x27;Network Error&#x27;</span>,</span><br><span class="line">    config,</span><br><span class="line">    <span class="literal">null</span>,</span><br><span class="line">    request</span><br><span class="line">  ))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">request.ontimeout = <span class="function"><span class="keyword">function</span> <span class="title">handleTimeout</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  reject(createError(</span><br><span class="line">    <span class="string">`Timeout of <span class="subst">$&#123;config.timeout&#125;</span> ms exceeded`</span>,</span><br><span class="line">    config,</span><br><span class="line">    <span class="string">&#x27;ECONNABORTED&#x27;</span>,</span><br><span class="line">    request</span><br><span class="line">  ))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">handleResponse</span>(<span class="params">response: AxiosResponse</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (response.status &gt;= <span class="number">200</span> &amp;&amp; response.status &lt; <span class="number">300</span>) &#123;</span><br><span class="line">    resolve(response)</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    reject(createError(</span><br><span class="line">      <span class="string">`Request failed with status code <span class="subst">$&#123;response.status&#125;</span>`</span>,</span><br><span class="line">      config,</span><br><span class="line">      <span class="literal">null</span>,</span><br><span class="line">      request,</span><br><span class="line">      response</span><br><span class="line">    ))</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="10-4-导出类型定义"><a href="#10-4-导出类型定义" class="headerlink" title="10.4 导出类型定义"></a>10.4 导出类型定义</h2><p>在 demo 中，TypeScript 并不能把 <code>e</code> 参数推断为 <code>AxiosError</code> 类型，于是我们需要手动指明类型，为了让外部应用能引入 <code>AxiosError</code> 类型，我们也需要把它们导出。</p>
<p>我们创建 <code>axios.ts</code> 文件，把之前的 <code>index.ts</code> 的代码拷贝过去，然后修改 <code>index.ts</code> 的代码。</p>
<p><code>index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;./axios&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">&#x27;./types&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> axios</span><br></pre></td></tr></table></figure>

<p>这样我们在 demo 中就可以引入 <code>AxiosError</code> 类型了。</p>
<p><code>examples/error/app.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios, &#123; AxiosError &#125; <span class="keyword">from</span> <span class="string">&#x27;../../src/index&#x27;</span></span><br><span class="line"></span><br><span class="line">axios(&#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/error/timeout&#x27;</span>,</span><br><span class="line">  timeout: <span class="number">2000</span></span><br><span class="line">&#125;).then(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res)</span><br><span class="line">&#125;).catch(<span class="function">(<span class="params">e: AxiosError</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(e.message)</span><br><span class="line">  <span class="built_in">console</span>.log(e.code)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>至此，我们关于 <code>ts-axios</code> 的异常处理逻辑就告一段落。下面的章节，我们会对 <code>ts-axios</code> 的接口做扩展，让它提供更多好用和方便的 API。</p>
<h1 id="11-扩展接口"><a href="#11-扩展接口" class="headerlink" title="11.扩展接口"></a>11.扩展接口</h1><h2 id="11-1-需求分析"><a href="#11-1-需求分析" class="headerlink" title="11.1 需求分析"></a>11.1 需求分析</h2><p>为了用户更加方便地使用 axios 发送请求，我们可以为所有支持请求方法扩展一些接口：</p>
<ul>
<li><code>axios.request(config)</code></li>
<li><code>axios.get(url[, config])</code></li>
<li><code>axios.delete(url[, config])</code></li>
<li><code>axios.head(url[, config])</code></li>
<li><code>axios.options(url[, config])</code></li>
<li><code>axios.post(url[, data[, config]])</code></li>
<li><code>axios.put(url[, data[, config]])</code></li>
<li><code>axios.patch(url[, data[, config]])</code></li>
</ul>
<p>如果使用了这些方法，我们就不必在 <code>config</code> 中指定 <code>url</code>、<code>method</code>、<code>data</code> 这些属性了。</p>
<p>从需求上来看，<code>axios</code> 不再单单是一个方法，更像是一个混合对象，本身是一个方法，又有很多方法属性，接下来我们就来实现这个混合对象。</p>
<h2 id="11-2-接口类型定义"><a href="#11-2-接口类型定义" class="headerlink" title="11.2 接口类型定义"></a>11.2 接口类型定义</h2><p>根据需求分析，混合对象 <code>axios</code> 本身是一个函数，我们再实现一个包括它属性方法的类，然后把这个类的原型属性和自身属性再拷贝到 <code>axios</code> 上。</p>
<p>我们先来给 <code>axios</code> 混合对象定义接口：</p>
<p><code>types/index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> Axios &#123;</span><br><span class="line">  request(config: AxiosRequestConfig): AxiosPromise</span><br><span class="line"></span><br><span class="line">  get(url: <span class="built_in">string</span>, config?: AxiosRequestConfig): AxiosPromise</span><br><span class="line"></span><br><span class="line">  <span class="keyword">delete</span>(url: <span class="built_in">string</span>, config?: AxiosRequestConfig): AxiosPromise</span><br><span class="line"></span><br><span class="line">  head(url: <span class="built_in">string</span>, config?: AxiosRequestConfig): AxiosPromise</span><br><span class="line"></span><br><span class="line">  options(url: <span class="built_in">string</span>, config?: AxiosRequestConfig): AxiosPromise</span><br><span class="line"></span><br><span class="line">  post(url: <span class="built_in">string</span>, data?: <span class="built_in">any</span>, config?: AxiosRequestConfig): AxiosPromise</span><br><span class="line"></span><br><span class="line">  put(url: <span class="built_in">string</span>, data?: <span class="built_in">any</span>, config?: AxiosRequestConfig): AxiosPromise</span><br><span class="line"></span><br><span class="line">  patch(url: <span class="built_in">string</span>, data?: <span class="built_in">any</span>, config?: AxiosRequestConfig): AxiosPromise</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosInstance <span class="keyword">extends</span> Axios &#123;</span><br><span class="line">  (config: AxiosRequestConfig): AxiosPromise</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosRequestConfig &#123;</span><br><span class="line">  url?: <span class="built_in">string</span></span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>首先定义一个 <code>Axios</code> 类型接口，它描述了 <code>Axios</code> 类中的公共方法，接着定义了 <code>AxiosInstance</code> 接口继承 <code>Axios</code>，它就是一个混合类型的接口。</p>
<p>另外 <code>AxiosRequestConfig</code> 类型接口中的 <code>url</code> 属性变成了可选属性。</p>
<h2 id="11-3-创建-Axios-类"><a href="#11-3-创建-Axios-类" class="headerlink" title="11.3 创建 Axios 类"></a>11.3 创建 Axios 类</h2><p>我们创建一个 <code>Axios</code> 类，来实现接口定义的公共方法。我们创建了一个 <code>core</code> 目录，用来存放发送请求核心流程的代码。我们在 <code>core</code> 目录下创建 <code>Axios.ts</code> 文件。</p>
<p><code>core/Axios.ts</code></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line">import &#123; AxiosRequestConfig, AxiosPromise, Method &#125; from &#39;..&#x2F;types&#39;</span><br><span class="line">import dispatchRequest from &#39;.&#x2F;dispatchRequest&#39;</span><br><span class="line"></span><br><span class="line">export default class Axios &#123;</span><br><span class="line">  request(config: AxiosRequestConfig): AxiosPromise &#123;</span><br><span class="line">    return dispatchRequest(config)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  get(url: string, config?: AxiosRequestConfig): AxiosPromise &#123;</span><br><span class="line">    return this._requestMethodWithoutData(&#39;get&#39;, url, config)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  delete(url: string, config?: AxiosRequestConfig): AxiosPromise &#123;</span><br><span class="line">    return this._requestMethodWithoutData(&#39;delete&#39;, url, config)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  head(url: string, config?: AxiosRequestConfig): AxiosPromise &#123;</span><br><span class="line">    return this._requestMethodWithoutData(&#39;head&#39;, url, config)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  options(url: string, config?: AxiosRequestConfig): AxiosPromise &#123;</span><br><span class="line">    return this._requestMethodWithoutData(&#39;options&#39;, url, config)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise &#123;</span><br><span class="line">    return this._requestMethodWithData(&#39;post&#39;, url, data, config)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise &#123;</span><br><span class="line">    return this._requestMethodWithData(&#39;put&#39;, url, data, config)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise &#123;</span><br><span class="line">    return this._requestMethodWithData(&#39;patch&#39;, url, data, config)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  _requestMethodWithoutData(method: Method, url: string, config?: AxiosRequestConfig) &#123;</span><br><span class="line">    return this.request(</span><br><span class="line">      Object.assign(config || &#123;&#125;, &#123;</span><br><span class="line">        method,</span><br><span class="line">        url</span><br><span class="line">      &#125;)</span><br><span class="line">    )</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  _requestMethodWithData(method: Method, url: string, data?: any, config?: AxiosRequestConfig) &#123;</span><br><span class="line">    return this.request(</span><br><span class="line">      Object.assign(config || &#123;&#125;, &#123;</span><br><span class="line">        method,</span><br><span class="line">        url,</span><br><span class="line">        data</span><br><span class="line">      &#125;)</span><br><span class="line">    )</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>其中 <code>request</code> 方法的功能和我们之前的 <code>axios</code> 函数功能是一致。<code>axios</code> 函数的功能就是发送请求，基于模块化编程的思想，我们把这部分功能抽出一个单独的模块，在 <code>core</code> 目录下创建 <code>dispatchRequest</code> 方法，把之前 <code>axios.ts</code> 的相关代码拷贝过去。另外我们把 <code>xhr.ts</code> 文件也迁移到 <code>core</code> 目录下。</p>
<p><code>core/dispatchRequest.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; AxiosPromise, AxiosRequestConfig, AxiosResponse &#125; <span class="keyword">from</span> <span class="string">&#x27;../types&#x27;</span></span><br><span class="line"><span class="keyword">import</span> xhr <span class="keyword">from</span> <span class="string">&#x27;./xhr&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; buildURL &#125; <span class="keyword">from</span> <span class="string">&#x27;../helpers/url&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; transformRequest, transformResponse &#125; <span class="keyword">from</span> <span class="string">&#x27;../helpers/data&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; processHeaders &#125; <span class="keyword">from</span> <span class="string">&#x27;../helpers/headers&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="function"><span class="keyword">function</span> <span class="title">dispatchRequest</span>(<span class="params">config: AxiosRequestConfig</span>): <span class="title">AxiosPromise</span> </span>&#123;</span><br><span class="line">  processConfig(config)</span><br><span class="line">  <span class="keyword">return</span> xhr(config).then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> transformResponseData(res)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">processConfig</span>(<span class="params">config: AxiosRequestConfig</span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">  config.url = transformURL(config)</span><br><span class="line">  config.headers = transformHeaders(config)</span><br><span class="line">  config.data = transformRequestData(config)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">transformURL</span>(<span class="params">config: AxiosRequestConfig</span>): <span class="title">string</span> </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; url, params &#125; = config</span><br><span class="line">  <span class="keyword">return</span> buildURL(url, params)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">transformRequestData</span>(<span class="params">config: AxiosRequestConfig</span>): <span class="title">any</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> transformRequest(config.data)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">transformHeaders</span>(<span class="params">config: AxiosRequestConfig</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; headers = &#123;&#125;, data &#125; = config</span><br><span class="line">  <span class="keyword">return</span> processHeaders(headers, data)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">transformResponseData</span>(<span class="params">res: AxiosResponse</span>): <span class="title">AxiosResponse</span> </span>&#123;</span><br><span class="line">  res.data = transformResponse(res.data)</span><br><span class="line">  <span class="keyword">return</span> res</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>回到 <code>Axios.ts</code> 文件，对于 <code>get</code>、<code>delete</code>、<code>head</code>、<code>options</code>、<code>post</code>、<code>patch</code>、<code>put</code> 这些方法，都是对外提供的语法糖，内部都是通过调用 <code>request</code> 方法实现发送请求，只不过在调用之前对 <code>config</code> 做了一层合并处理。</p>
<h2 id="11-4-混合对象实现"><a href="#11-4-混合对象实现" class="headerlink" title="11.4 混合对象实现"></a>11.4 混合对象实现</h2><p>混合对象实现思路很简单，首先这个对象是一个函数，其次这个对象要包括 <code>Axios</code> 类的所有原型属性和实例属性，我们首先来实现一个辅助函数 <code>extend</code>。</p>
<p><code>helpers/util.ts</code></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">export function extend&lt;T, U&gt;(to: T, from: U): T &amp; U &#123;</span><br><span class="line">  for (const key in from) &#123;</span><br><span class="line">    (to as T &amp; U)[key] &#x3D; from[key] as any</span><br><span class="line">  &#125;</span><br><span class="line">  return to as T &amp; U</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>extend</code> 方法的实现用到了交叉类型，并且用到了类型断言。<code>extend</code> 的最终目的是把 <code>from</code> 里的属性都扩展到 <code>to</code> 中，包括原型上的属性。</p>
<p>我们接下来对 <code>axios.ts</code> 文件做修改，我们用工厂模式去创建一个 <code>axios</code> 混合对象。</p>
<p><code>axios.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; AxiosInstance &#125; <span class="keyword">from</span> <span class="string">&#x27;./types&#x27;</span></span><br><span class="line"><span class="keyword">import</span> Axios <span class="keyword">from</span> <span class="string">&#x27;./core/Axios&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; extend &#125; <span class="keyword">from</span> <span class="string">&#x27;./helpers/util&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createInstance</span>(<span class="params"></span>): <span class="title">AxiosInstance</span> </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> context = <span class="keyword">new</span> Axios()</span><br><span class="line">  <span class="keyword">const</span> instance = Axios.prototype.request.bind(context)</span><br><span class="line"></span><br><span class="line">  extend(instance, context)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> instance <span class="keyword">as</span> AxiosInstance</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> axios = createInstance()</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> axios</span><br></pre></td></tr></table></figure>

<p>在 <code>createInstance</code> 工厂函数的内部，我们首先实例化了 <code>Axios</code> 实例 <code>context</code>，接着创建<code>instance</code> 指向 <code>Axios.prototype.request</code> 方法，并绑定了上下文 <code>context</code>；接着通过 <code>extend</code> 方法把 <code>context</code> 中的原型方法和实例方法全部拷贝到 <code>instance</code> 上，这样就实现了一个混合对象：<code>instance</code> 本身是一个函数，又拥有了 <code>Axios</code> 类的所有原型和实例属性，最终把这个 <code>instance</code> 返回。由于这里 <code>TypeScript</code> 不能正确推断 <code>instance</code> 的类型，我们把它断言成 <code>AxiosInstance</code> 类型。</p>
<p>这样我们就可以通过 <code>createInstance</code> 工厂函数创建了 <code>axios</code>，当直接调用 <code>axios</code> 方法就相当于执行了 <code>Axios</code> 类的 <code>request</code> 方法发送请求，当然我们也可以调用 <code>axios.get</code>、<code>axios.post</code> 等方法。</p>
<h2 id="11-5-demo-编写"><a href="#11-5-demo-编写" class="headerlink" title="11.5 demo 编写"></a>11.5 demo 编写</h2><p>在 <code>examples</code> 目录下创建 <code>extend</code> 目录，在 <code>extend</code> 目录下创建 <code>index.html</code>:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Extend example<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;/__build__/extend.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>接着创建 <code>app.ts</code> 作为入口文件：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;../../src/index&#x27;</span></span><br><span class="line"></span><br><span class="line">axios(&#123;</span><br><span class="line">  url: <span class="string">&#x27;/extend/post&#x27;</span>,</span><br><span class="line">  method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">  data: &#123;</span><br><span class="line">    msg: <span class="string">&#x27;hi&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">axios.request(&#123;</span><br><span class="line">  url: <span class="string">&#x27;/extend/post&#x27;</span>,</span><br><span class="line">  method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">  data: &#123;</span><br><span class="line">    msg: <span class="string">&#x27;hello&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">axios.get(<span class="string">&#x27;/extend/get&#x27;</span>)</span><br><span class="line"></span><br><span class="line">axios.options(<span class="string">&#x27;/extend/options&#x27;</span>)</span><br><span class="line"></span><br><span class="line">axios.delete(<span class="string">&#x27;/extend/delete&#x27;</span>)</span><br><span class="line"></span><br><span class="line">axios.head(<span class="string">&#x27;/extend/head&#x27;</span>)</span><br><span class="line"></span><br><span class="line">axios.post(<span class="string">&#x27;/extend/post&#x27;</span>, &#123; <span class="attr">msg</span>: <span class="string">&#x27;post&#x27;</span> &#125;)</span><br><span class="line"></span><br><span class="line">axios.put(<span class="string">&#x27;/extend/put&#x27;</span>, &#123; <span class="attr">msg</span>: <span class="string">&#x27;put&#x27;</span> &#125;)</span><br><span class="line"></span><br><span class="line">axios.patch(<span class="string">&#x27;/extend/patch&#x27;</span>, &#123; <span class="attr">msg</span>: <span class="string">&#x27;patch&#x27;</span> &#125;)</span><br></pre></td></tr></table></figure>

<p>在 <code>server.ts</code> 中增加对应的请求</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line">router.get(<span class="string">&#x27;/extend/get&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  res.json(&#123;</span><br><span class="line">    msg: <span class="string">&#x27;hello world&#x27;</span></span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">router.options(<span class="string">&#x27;/extend/options&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  res.end()</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">router.head(<span class="string">&#x27;/extend/head&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  res.end()</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">router.delete(<span class="string">&#x27;/extend/delete&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  res.end()</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">router.post(<span class="string">&#x27;/extend/post&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  res.json(req.body)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">router.put(<span class="string">&#x27;/extend/put&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  res.json(req.body)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">router.patch(<span class="string">&#x27;/extend/patch&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  res.json(req.body)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 响应数据支持泛型接口</span></span><br><span class="line">router.get(<span class="string">&#x27;/extend/user&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  res.json(&#123;</span><br><span class="line">    code: <span class="number">0</span>,</span><br><span class="line">    message: <span class="string">&#x27;ok&#x27;</span>,</span><br><span class="line">    result: &#123;</span><br><span class="line">      name: <span class="string">&#x27;Alice&#x27;</span>,</span><br><span class="line">      age: <span class="number">18</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>然后在命令行运行 <code>npm run dev</code>，接着打开 chrome 浏览器，访问 <code>http://localhost:8080/</code> 即可访问我们的 demo 了，我们点到 <code>Extend</code> 目录下，通过开发者工具的 network 部分我们可以看到每个请求的发送情况。</p>
<p>至此我们支持了对 <code>axios</code> API 的扩展，把它变成了一个混合对象。官方的 <code>axios</code> 实例除了支持了 <code>axios(config)</code>，还支持了传入 2 个参数 <code>axios(url, config)</code>，这里就涉及到函数重载的概念了，下一节我们来实现这个 feature。</p>
<h1 id="12-axios-函数重载"><a href="#12-axios-函数重载" class="headerlink" title="12.axios 函数重载"></a>12.axios 函数重载</h1><h2 id="12-1-需求分析"><a href="#12-1-需求分析" class="headerlink" title="12.1 需求分析"></a>12.1 需求分析</h2><p>目前我们的 axios 函数只支持传入 1 个参数，如下：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">axios(&#123;</span><br><span class="line">  url: <span class="string">&#x27;/extend/post&#x27;</span>,</span><br><span class="line">  method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">  data: &#123;</span><br><span class="line">    msg: <span class="string">&#x27;hi&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>我们希望该函数也能支持传入 2 个参数，如下：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">axios(<span class="string">&#x27;/extend/post&#x27;</span>, &#123;</span><br><span class="line">  method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">  data: &#123;</span><br><span class="line">    msg: <span class="string">&#x27;hello&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>第一个参数是 <code>url</code>，第二个参数是 <code>config</code>，这个函数有点类似 <code>axios.get</code> 方法支持的参数类型，不同的是如果我们想要指定 HTTP 方法类型，仍然需要在 <code>config</code> 传入 <code>method</code>。</p>
<p>这就用到我们之前所学的函数重载知识点了，接下来我们来实现它。</p>
<h2 id="12-2-重载实现"><a href="#12-2-重载实现" class="headerlink" title="12.2 重载实现"></a>12.2 重载实现</h2><p>首先我们要修改 <code>AxiosInstance</code> 的类型定义。</p>
<p><code>types/index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosInstance <span class="keyword">extends</span> Axios &#123;</span><br><span class="line">  (config: AxiosRequestConfig): AxiosPromise</span><br><span class="line"></span><br><span class="line">  (url: <span class="built_in">string</span>, config?: AxiosRequestConfig): AxiosPromise</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们增加一种函数的定义，它支持 2 个参数，其中 <code>url</code> 是必选参数，<code>config</code> 是可选参数。</p>
<p>由于 <code>axios</code> 函数实际上指向的是 <code>request</code> 函数，所以我们来修改 <code>request</code> 函数的实现。</p>
<p><code>core/Axios.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">request(url: <span class="built_in">any</span>, config?: <span class="built_in">any</span>): AxiosPromise &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">typeof</span> url === <span class="string">&#x27;string&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!config) &#123;</span><br><span class="line">      config = &#123;&#125;</span><br><span class="line">    &#125;</span><br><span class="line">    config.url = url</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    config = url</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> dispatchRequest(config)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们把 <code>request</code> 函数的参数改成 2 个，<code>url</code> 和 <code>config</code> 都是 <code>any</code> 类型，<code>config</code> 还是可选参数。</p>
<p>接着在函数体我们判断 <code>url</code> 是否为字符串类型，一旦它为字符串类型，则继续对 <code>config</code> 判断，因为它可能不传，如果为空则构造一个空对象，然后把 <code>url</code> 添加到 <code>config.url</code> 中。如果 <code>url</code> 不是字符串类型，则说明我们传入的就是单个参数，且 <code>url</code> 就是 <code>config</code>，因此把 <code>url</code> 赋值给 <code>config</code>。</p>
<p>这里要注意的是，我们虽然修改了 <code>request</code> 的实现，支持了 2 种参数，但是我们对外提供的 <code>request</code> 接口仍然不变，可以理解为这仅仅是内部的实现的修改，与对外接口不必一致，只要保留实现兼容接口即可。</p>
<h2 id="12-3-编写-demo"><a href="#12-3-编写-demo" class="headerlink" title="12.3 编写 demo"></a>12.3 编写 demo</h2><p><code>examples/extend/app.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">axios(&#123;</span><br><span class="line">  url: <span class="string">&#x27;/extend/post&#x27;</span>,</span><br><span class="line">  method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">  data: &#123;</span><br><span class="line">    msg: <span class="string">&#x27;hi&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">axios(<span class="string">&#x27;/extend/post&#x27;</span>, &#123;</span><br><span class="line">  method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">  data: &#123;</span><br><span class="line">    msg: <span class="string">&#x27;hello&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>我们使用了 <code>axios</code> 2 种请求方式，打开页面运行 <code>demo</code>，通过 network 我们可以看到 2 种请求都是运行正常的。</p>
<p>至此我们实现了 <code>axios</code> 函数的重载。官方 axios 支持了一种能力，我们可以去定义返回数据的类型，并在请求的时候指定该类型，然后在响应数据中我们就可以获取到该数据类型。下一节课我们就来实现这个 feature。</p>
<h1 id="13-响应数据支持泛型"><a href="#13-响应数据支持泛型" class="headerlink" title="13.响应数据支持泛型"></a>13.响应数据支持泛型</h1><h2 id="13-1-需求分析"><a href="#13-1-需求分析" class="headerlink" title="13.1 需求分析"></a>13.1 需求分析</h2><p>通常情况下，我们会把后端返回数据格式单独放入一个接口中：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 请求接口数据</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> ResponseData&lt;T = any&gt; &#123;</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 状态码</span></span><br><span class="line"><span class="comment">   * <span class="doctag">@type <span class="type">&#123; number &#125;</span></span></span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  code: <span class="built_in">number</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 数据</span></span><br><span class="line"><span class="comment">   * <span class="doctag">@type <span class="type">&#123; T &#125;</span></span></span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  result: T</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 消息</span></span><br><span class="line"><span class="comment">   * <span class="doctag">@type <span class="type">&#123; string &#125;</span></span></span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  message: <span class="built_in">string</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们可以把 API 抽离成单独的模块：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; ResponseData &#125; <span class="keyword">from</span> <span class="string">&#x27;./interface.ts&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">getUser</span>&lt;<span class="title">T</span>&gt;(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> axios.get&lt;ResponseData&lt;T&gt;&gt;(<span class="string">&#x27;/somepath&#x27;</span>)</span><br><span class="line">    .then(<span class="function"><span class="params">res</span> =&gt;</span> res.data)</span><br><span class="line">    .catch(<span class="function"><span class="params">err</span> =&gt;</span> <span class="built_in">console</span>.error(err))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>接着我们写入返回的数据类型 <code>User</code>，这可以让 TypeScript 顺利推断出我们想要的类型：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> User &#123;</span><br><span class="line">  name: <span class="built_in">string</span></span><br><span class="line">  age: <span class="built_in">number</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">test</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="comment">// user 被推断出为</span></span><br><span class="line">  <span class="comment">// &#123;</span></span><br><span class="line">  <span class="comment">//  code: number,</span></span><br><span class="line">  <span class="comment">//  result: &#123; name: string, age: number &#125;,</span></span><br><span class="line">  <span class="comment">//  message: string</span></span><br><span class="line">  <span class="comment">// &#125;</span></span><br><span class="line">  <span class="keyword">const</span> user = <span class="keyword">await</span> getUser&lt;User&gt;()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="13-2-接口添加泛型参数"><a href="#13-2-接口添加泛型参数" class="headerlink" title="13.2 接口添加泛型参数"></a>13.2 接口添加泛型参数</h2><p>根据需求分析，我们需要给相关的接口定义添加泛型参数。</p>
<p><code>types/index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosResponse&lt;T = any&gt; &#123;</span><br><span class="line">  data: T</span><br><span class="line">  status: <span class="built_in">number</span></span><br><span class="line">  statusText: <span class="built_in">string</span></span><br><span class="line">  headers: <span class="built_in">any</span></span><br><span class="line">  config: AxiosRequestConfig</span><br><span class="line">  request: <span class="built_in">any</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosPromise&lt;T = any&gt; <span class="keyword">extends</span> Promise&lt;AxiosResponse&lt;T&gt;&gt; &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> Axios &#123;</span><br><span class="line">  request&lt;T = <span class="built_in">any</span>&gt;(config: AxiosRequestConfig): AxiosPromise&lt;T&gt;</span><br><span class="line"></span><br><span class="line">  get&lt;T = <span class="built_in">any</span>&gt;(url: <span class="built_in">string</span>, config?: AxiosRequestConfig): AxiosPromise&lt;T&gt;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">delete</span>&lt;T = <span class="built_in">any</span>&gt;(url: <span class="built_in">string</span>, config?: AxiosRequestConfig): AxiosPromise&lt;T&gt;</span><br><span class="line"></span><br><span class="line">  head&lt;T = <span class="built_in">any</span>&gt;(url: <span class="built_in">string</span>, config?: AxiosRequestConfig): AxiosPromise&lt;T&gt;</span><br><span class="line"></span><br><span class="line">  options&lt;T = <span class="built_in">any</span>&gt;(url: <span class="built_in">string</span>, config?: AxiosRequestConfig): AxiosPromise&lt;T&gt;</span><br><span class="line"></span><br><span class="line">  post&lt;T = <span class="built_in">any</span>&gt;(url: <span class="built_in">string</span>, data?: <span class="built_in">any</span>, config?: AxiosRequestConfig): AxiosPromise&lt;T&gt;</span><br><span class="line"></span><br><span class="line">  put&lt;T = <span class="built_in">any</span>&gt;(url: <span class="built_in">string</span>, data?: <span class="built_in">any</span>, config?: AxiosRequestConfig): AxiosPromise&lt;T&gt;</span><br><span class="line"></span><br><span class="line">  patch&lt;T = <span class="built_in">any</span>&gt;(url: <span class="built_in">string</span>, data?: <span class="built_in">any</span>, config?: AxiosRequestConfig): AxiosPromise&lt;T&gt;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosInstance <span class="keyword">extends</span> Axios &#123;</span><br><span class="line">  &lt;T = <span class="built_in">any</span>&gt;(config: AxiosRequestConfig): AxiosPromise&lt;T&gt;</span><br><span class="line"></span><br><span class="line">  &lt;T = <span class="built_in">any</span>&gt;(url: <span class="built_in">string</span>, config?: AxiosRequestConfig): AxiosPromise&lt;T&gt;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里我们先给 <code>AxiosResponse</code> 接口添加了泛型参数 <code>T</code>，<code>T=any</code> 表示泛型的类型参数默认值为 <code>any</code>。</p>
<p>接着我们为 <code>AxiosPromise</code>、<code>Axios</code> 以及 <code>AxiosInstance</code> 接口都加上了泛型参数。我们可以看到这些请求的返回类型都变成了 <code>AxiosPromise&lt;T&gt;</code>，也就是 <code>Promise&lt;AxiosResponse&lt;T&gt;&gt;</code>，这样我们就可以从响应中拿到了类型 <code>T</code> 了。</p>
<h2 id="13-3-demo-编写"><a href="#13-3-demo-编写" class="headerlink" title="13.3 demo 编写"></a>13.3 demo 编写</h2><p><code>examples/extend/app.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> ResponseData&lt;T = any&gt; &#123;</span><br><span class="line">  code: <span class="built_in">number</span></span><br><span class="line">  result: T</span><br><span class="line">  message: <span class="built_in">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> User &#123;</span><br><span class="line">  name: <span class="built_in">string</span></span><br><span class="line">  age: <span class="built_in">number</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getUser</span>&lt;<span class="title">T</span>&gt;(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> axios&lt;ResponseData&lt;T&gt;&gt;(<span class="string">&#x27;/extend/user&#x27;</span>)</span><br><span class="line">    .then(<span class="function"><span class="params">res</span> =&gt;</span> res.data)</span><br><span class="line">    .catch(<span class="function"><span class="params">err</span> =&gt;</span> <span class="built_in">console</span>.error(err))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">test</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> user = <span class="keyword">await</span> getUser&lt;User&gt;()</span><br><span class="line">  <span class="keyword">if</span> (user) &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(user.result.name)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">test()</span><br></pre></td></tr></table></figure>

<p>当我们调用 <code>getUser&lt;User&gt;</code> 的时候，相当于调用了 <code>axios&lt;ResponseData&lt;User&gt;&gt;</code>，也就是我们传入给 <code>axios</code> 函数的类型 <code>T</code> 为 <code>ResponseData&lt;User&gt;</code>；相当于返回值 <code>AxiosPromise&lt;T&gt;</code> 的 <code>T</code>，实际上也是 <code>Promise&lt;AxiosResponse&lt;T&gt;&gt;</code> 中的 <code>T</code> 的类型是 <code>ResponseData&lt;User&gt;</code>，所以响应数据中的 <code>data</code> 类型就是 <code>ResponseData&lt;User&gt;</code>，也就是如下数据结构：</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  code: number</span><br><span class="line">  result: User</span><br><span class="line">  message: string</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这个也是 <code>const user = await getUser&lt;User&gt;()</code> 返回值 <code>user</code> 的数据类型，所以 TypeScript 能正确推断出 <code>user</code> 的类型。</p>
<p>至此，我们的 <code>ts-axios</code> 接口扩展章节就告一段落了，下一章我们来实现 <code>axios</code> 的一个非常好用的功能 —— 拦截器。</p>
<h1 id="14-拦截器设计与实现"><a href="#14-拦截器设计与实现" class="headerlink" title="14.拦截器设计与实现"></a>14.拦截器设计与实现</h1><h2 id="14-1-需求分析"><a href="#14-1-需求分析" class="headerlink" title="14.1 需求分析"></a>14.1 需求分析</h2><p>我们希望能对请求的发送和响应做拦截，也就是在发送请求之前和接收到响应之后做一些额外逻辑。</p>
<p>我们希望设计的拦截器的使用方式如下：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 添加一个请求拦截器</span></span><br><span class="line">axios.interceptors.request.use(<span class="function"><span class="keyword">function</span> (<span class="params">config</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 在发送请求之前可以做一些事情</span></span><br><span class="line">  <span class="keyword">return</span> config;</span><br><span class="line">&#125;, <span class="function"><span class="keyword">function</span> (<span class="params">error</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 处理请求错误</span></span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">Promise</span>.reject(error);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// 添加一个响应拦截器</span></span><br><span class="line">axios.interceptors.response.use(<span class="function"><span class="keyword">function</span> (<span class="params">response</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 处理响应数据</span></span><br><span class="line">  <span class="keyword">return</span> response;</span><br><span class="line">&#125;, <span class="function"><span class="keyword">function</span> (<span class="params">error</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 处理响应错误</span></span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">Promise</span>.reject(error);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>在 <code>axios</code> 对象上有一个 <code>interceptors</code> 对象属性，该属性又有 <code>request</code> 和 <code>response</code> 2 个属性，它们都有一个 <code>use</code> 方法，<code>use</code> 方法支持 2 个参数，第一个参数类似 Promise 的 <code>resolve</code> 函数，第二个参数类似 Promise 的 <code>reject</code> 函数。我们可以在 <code>resolve</code> 函数和 <code>reject</code> 函数中执行同步代码或者是异步代码逻辑。</p>
<p>并且我们是可以添加多个拦截器的，拦截器的执行顺序是链式依次执行的方式。对于 <code>request</code> 拦截器，后添加的拦截器会在请求前的过程中先执行；对于 <code>response</code> 拦截器，先添加的拦截器会在响应后先执行。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">axios.interceptors.request.use(<span class="function"><span class="params">config</span> =&gt;</span> &#123;</span><br><span class="line">  config.headers.test += <span class="string">&#x27;1&#x27;</span></span><br><span class="line">  <span class="keyword">return</span> config</span><br><span class="line">&#125;)</span><br><span class="line">axios.interceptors.request.use(<span class="function"><span class="params">config</span> =&gt;</span> &#123;</span><br><span class="line">  config.headers.test += <span class="string">&#x27;2&#x27;</span></span><br><span class="line">  <span class="keyword">return</span> config</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>此外，我们也可以支持删除某个拦截器，如下：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> myInterceptor = axios.interceptors.request.use(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;<span class="comment">/*...*/</span>&#125;)</span><br><span class="line">axios.interceptors.request.eject(myInterceptor)</span><br></pre></td></tr></table></figure>

<h2 id="14-2-整体设计"><a href="#14-2-整体设计" class="headerlink" title="14.2 整体设计"></a>14.2 整体设计</h2><p>我们先用一张图来展示一下拦截器工作流程：</p>
<p><img data-src="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20210520111405.png" alt="img"></p>
<p>整个过程是一个链式调用的方式，并且每个拦截器都可以支持同步和异步处理，我们自然而然地就联想到使用 Promise 链的方式来实现整个调用过程。</p>
<p>在这个 Promise 链的执行过程中，请求拦截器 <code>resolve</code> 函数处理的是 <code>config</code> 对象，而相应拦截器 <code>resolve</code> 函数处理的是 <code>response</code> 对象。</p>
<p>在了解了拦截器工作流程后，我们先要创建一个拦截器管理类，允许我们去添加 删除和遍历拦截器。</p>
<h2 id="14-3-拦截器管理类实现"><a href="#14-3-拦截器管理类实现" class="headerlink" title="14.3 拦截器管理类实现"></a>14.3 拦截器管理类实现</h2><p>根据需求，<code>axios</code> 拥有一个 <code>interceptors</code> 对象属性，该属性又有 <code>request</code> 和 <code>response</code> 2 个属性，它们对外提供一个 <code>use</code> 方法来添加拦截器，我们可以把这俩属性看做是一个拦截器管理对象。<code>use</code> 方法支持 2 个参数，第一个是 <code>resolve</code> 函数，第二个是 <code>reject</code> 函数，对于 <code>resolve</code> 函数的参数，请求拦截器是 <code>AxiosRequestConfig</code> 类型的，而响应拦截器是 <code>AxiosResponse</code> 类型的；而对于 <code>reject</code> 函数的参数类型则是 <code>any</code> 类型的。</p>
<p>根据上述分析，我们先来定义一下拦截器管理对象对外的接口。</p>
<h3 id="14-3-1-接口定义"><a href="#14-3-1-接口定义" class="headerlink" title="14.3.1 接口定义"></a>14.3.1 接口定义</h3><p><code>types/index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosInterceptorManager&lt;T&gt; &#123;</span><br><span class="line">  use(resolved: ResolvedFn&lt;T&gt;, rejected?: RejectedFn): <span class="built_in">number</span></span><br><span class="line"></span><br><span class="line">  eject(id: <span class="built_in">number</span>): <span class="built_in">void</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> ResolvedFn&lt;T=any&gt; &#123;</span><br><span class="line">  (val: T): T | <span class="built_in">Promise</span>&lt;T&gt;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> RejectedFn &#123;</span><br><span class="line">  (error: <span class="built_in">any</span>): <span class="built_in">any</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> Axios &#123;</span><br><span class="line">  interceptors: &#123;</span><br><span class="line">    request: AxiosInterceptorManager&lt;AxiosRequestConfig&gt;</span><br><span class="line">    response: AxiosInterceptorManager&lt;AxiosResponse&gt;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里我们定义了 <code>AxiosInterceptorManager</code> 泛型接口，因为对于 <code>resolve</code> 函数的参数，请求拦截器和响应拦截器是不同的。</p>
<h3 id="14-3-2-代码实现"><a href="#14-3-2-代码实现" class="headerlink" title="14.3.2 代码实现"></a>14.3.2 代码实现</h3><p><code>core/InterceptorManager.ts</code></p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; ResolvedFn, RejectedFn &#125; <span class="keyword">from</span> <span class="string">&#x27;../types&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> Interceptor&lt;T&gt; &#123;</span><br><span class="line">  resolved: ResolvedFn&lt;T&gt;</span><br><span class="line">  rejected?: RejectedFn</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="class"><span class="keyword">class</span> <span class="title">InterceptorManager</span>&lt;<span class="title">T</span>&gt; </span>&#123;</span><br><span class="line">  <span class="keyword">private</span> interceptors: <span class="built_in">Array</span>&lt;Interceptor&lt;T&gt; | <span class="literal">null</span>&gt;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="title">constructor</span>(<span class="params"></span>)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.interceptors = []</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  use(resolved: ResolvedFn&lt;T&gt;, rejected?: RejectedFn): <span class="built_in">number</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.interceptors.push(&#123;</span><br><span class="line">      resolved,</span><br><span class="line">      rejected</span><br><span class="line">    &#125;)</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">this</span>.interceptors.length - <span class="number">1</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  forEach(fn: <span class="function">(<span class="params">interceptor: Interceptor&lt;T&gt;</span>) =&gt;</span> <span class="built_in">void</span>): <span class="built_in">void</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.interceptors.forEach(<span class="function"><span class="params">interceptor</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (interceptor !== <span class="literal">null</span>) &#123;</span><br><span class="line">        fn(interceptor)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  eject(id: <span class="built_in">number</span>): <span class="built_in">void</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span>.interceptors[id]) &#123;</span><br><span class="line">      <span class="built_in">this</span>.interceptors[id] = <span class="literal">null</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们定义了一个 <code>InterceptorManager</code> 泛型类，内部维护了一个私有属性 <code>interceptors</code>，它是一个数组，用来存储拦截器。该类还对外提供了 3 个方法，其中 <code>use</code> 接口就是添加拦截器到 <code>interceptors</code> 中，并返回一个 <code>id</code> 用于删除；<code>forEach</code> 接口就是遍历 <code>interceptors</code> 用的，它支持传入一个函数，遍历过程中会调用该函数，并把每一个 <code>interceptor</code> 作为该函数的参数传入；<code>eject</code> 就是删除一个拦截器，通过传入拦截器的 <code>id</code> 删除。</p>
<h2 id="14-4-链式调用实现"><a href="#14-4-链式调用实现" class="headerlink" title="14.4 链式调用实现"></a>14.4 链式调用实现</h2><blockquote>
<p>本小节需要你对 Promise 掌握和理解，可以前往 <a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">MDN</a>学习。</p>
</blockquote>
<p>当我们实现好拦截器管理类，接下来就是在 <code>Axios</code> 中定义一个 <code>interceptors</code> 属性，它的类型如下：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Interceptors &#123;</span><br><span class="line">  request: InterceptorManager&lt;AxiosRequestConfig&gt;</span><br><span class="line">  response: InterceptorManager&lt;AxiosResponse&gt;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="class"><span class="keyword">class</span> <span class="title">Axios</span> </span>&#123;</span><br><span class="line">  interceptors: Interceptors</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="title">constructor</span>(<span class="params"></span>)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.interceptors = &#123;</span><br><span class="line">      request: <span class="keyword">new</span> InterceptorManager&lt;AxiosRequestConfig&gt;(),</span><br><span class="line">      response: <span class="keyword">new</span> InterceptorManager&lt;AxiosResponse&gt;()</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>Interceptors</code> 类型拥有 2 个属性，一个请求拦截器管理类实例，一个是响应拦截器管理类实例。我们在实例化 <code>Axios</code> 类的时候，在它的构造器去初始化这个 <code>interceptors</code> 实例属性。</p>
<p>接下来，我们修改 <code>request</code> 方法的逻辑，添加拦截器链式调用的逻辑：</p>
<p><code>core/Axios.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> PromiseChain &#123;</span><br><span class="line">  resolved: ResolvedFn | (<span class="function">(<span class="params">config: AxiosRequestConfig</span>) =&gt;</span> AxiosPromise)</span><br><span class="line">  rejected?: RejectedFn</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">request(url: <span class="built_in">any</span>, config?: <span class="built_in">any</span>): AxiosPromise &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">typeof</span> url === <span class="string">&#x27;string&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!config) &#123;</span><br><span class="line">      config = &#123;&#125;</span><br><span class="line">    &#125;</span><br><span class="line">    config.url = url</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    config = url</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> chain: PromiseChain[] = [&#123;</span><br><span class="line">    resolved: dispatchRequest,</span><br><span class="line">    rejected: <span class="literal">undefined</span></span><br><span class="line">  &#125;]</span><br><span class="line"></span><br><span class="line">  <span class="built_in">this</span>.interceptors.request.forEach(<span class="function"><span class="params">interceptor</span> =&gt;</span> &#123;</span><br><span class="line">    chain.unshift(interceptor)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  <span class="built_in">this</span>.interceptors.response.forEach(<span class="function"><span class="params">interceptor</span> =&gt;</span> &#123;</span><br><span class="line">    chain.push(interceptor)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> promise = <span class="built_in">Promise</span>.resolve(config)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">while</span> (chain.length) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; resolved, rejected &#125; = chain.shift()!</span><br><span class="line">    promise = promise.then(resolved, rejected)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> promise</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>首先，构造一个 <code>PromiseChain</code> 类型的数组 <code>chain</code>，并把 <code>dispatchRequest</code> 函数赋值给 <code>resolved</code> 属性；接着先遍历请求拦截器插入到 <code>chain</code> 的前面；然后再遍历响应拦截器插入到 <code>chain</code> 后面。</p>
<p>接下来定义一个已经 resolve 的 <code>promise</code>，循环这个 <code>chain</code>，拿到每个拦截器对象，把它们的 <code>resolved</code> 函数和 <code>rejected</code> 函数添加到 <code>promise.then</code> 的参数中，这样就相当于通过 Promise 的链式调用方式，实现了拦截器一层层的链式调用的效果。</p>
<p>注意我们拦截器的执行顺序，对于请求拦截器，先执行后添加的，后执行先添加的；而对于响应拦截器，先执行先添加的，后执行后添加的。</p>
<h2 id="14-5-demo-编写"><a href="#14-5-demo-编写" class="headerlink" title="14.5 demo 编写"></a>14.5 demo 编写</h2><p>在 <code>examples</code> 目录下创建 <code>interceptor</code> 目录，在 <code>interceptor</code> 目录下创建 <code>index.html</code>:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Interceptor example<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;/__build__/interceptor.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>接着创建 <code>app.ts</code> 作为入口文件：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;../../src/index&#x27;</span></span><br><span class="line"></span><br><span class="line">axios.interceptors.request.use(<span class="function"><span class="params">config</span> =&gt;</span> &#123;</span><br><span class="line">  config.headers.test += <span class="string">&#x27;1&#x27;</span></span><br><span class="line">  <span class="keyword">return</span> config</span><br><span class="line">&#125;)</span><br><span class="line">axios.interceptors.request.use(<span class="function"><span class="params">config</span> =&gt;</span> &#123;</span><br><span class="line">  config.headers.test += <span class="string">&#x27;2&#x27;</span></span><br><span class="line">  <span class="keyword">return</span> config</span><br><span class="line">&#125;)</span><br><span class="line">axios.interceptors.request.use(<span class="function"><span class="params">config</span> =&gt;</span> &#123;</span><br><span class="line">  config.headers.test += <span class="string">&#x27;3&#x27;</span></span><br><span class="line">  <span class="keyword">return</span> config</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">axios.interceptors.response.use(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">  res.data += <span class="string">&#x27;1&#x27;</span></span><br><span class="line">  <span class="keyword">return</span> res</span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">let</span> interceptor = axios.interceptors.response.use(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">  res.data += <span class="string">&#x27;2&#x27;</span></span><br><span class="line">  <span class="keyword">return</span> res</span><br><span class="line">&#125;)</span><br><span class="line">axios.interceptors.response.use(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">  res.data += <span class="string">&#x27;3&#x27;</span></span><br><span class="line">  <span class="keyword">return</span> res</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">axios.interceptors.response.eject(interceptor)</span><br><span class="line"></span><br><span class="line">axios(&#123;</span><br><span class="line">  url: <span class="string">&#x27;/interceptor/get&#x27;</span>,</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">  headers: &#123;</span><br><span class="line">    test: <span class="string">&#x27;&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;).then(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res.data)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>该 demo 我们添加了 3 个请求拦截器，添加了 3 个响应拦截器并删除了第二个。</p>
<p>添加路由规则</p>
<p> <code>server.ts</code> </p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">router.get(<span class="string">&#x27;/interceptor/get&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  res.end(<span class="string">&#x27;hello &#x27;</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>运行该 demo 我们通过浏览器访问，我们发送的请求添加了一个 <code>test</code> 的请求 header，它的值是 <code>321</code>；我们的响应数据返回的是 <code>hello</code>，经过响应拦截器的处理，最终我们输出的数据是 <code>hello13</code>。</p>
<p>至此，我们给 <code>ts-axios</code> 实现了拦截器功能，它是一个非常实用的功能，在实际工作中我们可以利用它做一些需求如登录权限认证。</p>
<p>我们目前通过 <code>axios</code> 发送请求，往往会传入一堆配置，但是我们也希望 <code>ts-axios</code> 本身也会有一些默认配置，我们把用户传入的自定义配置和默认配置做一层合并。其实，大部分的 JS 库都是类似的玩法。下面一章我们就来实现这个 feature。</p>
<h1 id="15-合并配置的设计与实现"><a href="#15-合并配置的设计与实现" class="headerlink" title="15.合并配置的设计与实现"></a>15.合并配置的设计与实现</h1><h2 id="15-1-需求分析"><a href="#15-1-需求分析" class="headerlink" title="15.1 需求分析"></a>15.1 需求分析</h2><p>在之前的章节我们了解到，在发送请求的时候可以传入一个配置，来决定请求的不同行为。我们也希望 <code>ts-axios</code> 可以有默认配置，定义一些默认的行为。这样在发送每个请求，用户传递的配置可以和默认配置做一层合并。</p>
<p>和官网 <code>axios</code> 库保持一致，我们给 <code>axios</code> 对象添加一个 <code>defaults</code> 属性，表示默认配置，你甚至可以直接修改这些默认配置：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">axios.defaults.headers.common[<span class="string">&#x27;test&#x27;</span>] = <span class="number">123</span></span><br><span class="line">axios.defaults.headers.post[<span class="string">&#x27;Content-Type&#x27;</span>] = <span class="string">&#x27;application/x-www-form-urlencoded&#x27;</span></span><br><span class="line">axios.defaults.timeout = <span class="number">2000</span></span><br></pre></td></tr></table></figure>

<p>其中对于 <code>headers</code> 的默认配置支持 <code>common</code> 和一些请求 <code>method</code> 字段，<code>common</code> 表示对于任何类型的请求都要添加该属性，而 <code>method</code> 表示只有该类型请求方法才会添加对应的属性。</p>
<p>在上述例子中，我们会默认为所有请求的 <code>header</code> 添加 <code>test</code> 属性，会默认为 <code>post</code> 请求的 <code>header</code> 添加 <code>Content-Type</code> 属性。</p>
<h2 id="15-2-默认配置"><a href="#15-2-默认配置" class="headerlink" title="15.2 默认配置"></a>15.2 默认配置</h2><h3 id="15-2-1-默认配置定义"><a href="#15-2-1-默认配置定义" class="headerlink" title="15.2.1 默认配置定义"></a>15.2.1 默认配置定义</h3><p>接下来，我们先实现默认配置</p>
<p><code>src/defaults.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; AxiosRequestConfig &#125; <span class="keyword">from</span> <span class="string">&#x27;./types&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> defaults: AxiosRequestConfig = &#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line"></span><br><span class="line">  timeout: <span class="number">0</span>,</span><br><span class="line"></span><br><span class="line">  headers: &#123;</span><br><span class="line">    common: &#123;</span><br><span class="line">      Accept: <span class="string">&#x27;application/json, text/plain, */*&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> methodsNoData = [<span class="string">&#x27;delete&#x27;</span>, <span class="string">&#x27;get&#x27;</span>, <span class="string">&#x27;head&#x27;</span>, <span class="string">&#x27;options&#x27;</span>]</span><br><span class="line"></span><br><span class="line">methodsNoData.forEach(<span class="function"><span class="params">method</span> =&gt;</span> &#123;</span><br><span class="line">  defaults.headers[method] = &#123;&#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> methodsWithData = [<span class="string">&#x27;post&#x27;</span>, <span class="string">&#x27;put&#x27;</span>, <span class="string">&#x27;patch&#x27;</span>]</span><br><span class="line"></span><br><span class="line">methodsWithData.forEach(<span class="function"><span class="params">method</span> =&gt;</span> &#123;</span><br><span class="line">  defaults.headers[method] = &#123;</span><br><span class="line">    <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/x-www-form-urlencoded&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> defaults</span><br></pre></td></tr></table></figure>

<p>我们定义了 <code>defaults</code> 常量，它包含默认请求的方法、超时时间，以及 <code>headers</code> 配置。</p>
<p>未来我们会根据新的需求添加更多的默认配置。</p>
<h3 id="15-2-2-添加到-axios-对象中"><a href="#15-2-2-添加到-axios-对象中" class="headerlink" title="15.2.2 添加到 axios 对象中"></a>15.2.2 添加到 axios 对象中</h3><p>根据需求，我们要给 <code>axios</code> 对象添加一个 <code>defaults</code> 属性，表示默认配置：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="class"><span class="keyword">class</span> <span class="title">Axios</span> </span>&#123;</span><br><span class="line">  defaults: AxiosRequestConfig</span><br><span class="line">  interceptors: Interceptors</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="title">constructor</span>(<span class="params">initConfig: AxiosRequestConfig</span>)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.defaults = initConfig</span><br><span class="line">    <span class="built_in">this</span>.interceptors = &#123;</span><br><span class="line">      request: <span class="keyword">new</span> InterceptorManager&lt;AxiosRequestConfig&gt;(),</span><br><span class="line">      response: <span class="keyword">new</span> InterceptorManager&lt;AxiosResponse&gt;()</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;  </span><br></pre></td></tr></table></figure>

<p>我们给 <code>Axios</code> 类添加一个 <code>defaults</code> 成员属性，并且让 <code>Axios</code> 的构造函数接受一个 <code>initConfig</code> 对象，把 <code>initConfig</code> 赋值给 <code>this.defaults</code>。</p>
<p>接着修改 <code>createInstance</code> 方法，支持传入 <code>config</code> 对象。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> defaults <span class="keyword">from</span> <span class="string">&#x27;./defaults&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createInstance</span>(<span class="params">config: AxiosRequestConfig</span>): <span class="title">AxiosInstance</span> </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> context = <span class="keyword">new</span> Axios(config)</span><br><span class="line">  <span class="keyword">const</span> instance = Axios.prototype.request.bind(context)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// extend(instance, Axios.prototype, context)</span></span><br><span class="line"></span><br><span class="line">  extend(instance, context)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> instance <span class="keyword">as</span> AxiosInstance</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> axios = createInstance(defaults)</span><br></pre></td></tr></table></figure>

<p>这样我们就可以在执行 <code>createInstance</code> 创建 <code>axios</code> 对象的时候，把默认配置传入了。</p>
<h2 id="15-3-配置合并及策略"><a href="#15-3-配置合并及策略" class="headerlink" title="15.3 配置合并及策略"></a>15.3 配置合并及策略</h2><p>定义了默认配置后，我们发送每个请求的时候需要把自定义配置和默认配置做合并，它并不是简单的 2 个普通对象的合并，对于不同的字段合并，会有不同的合并策略。举个例子：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">config1 = &#123;</span><br><span class="line">  method: <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line"></span><br><span class="line">  timeout: <span class="number">0</span>,</span><br><span class="line"></span><br><span class="line">  headers: &#123;</span><br><span class="line">    common: &#123;</span><br><span class="line">      Accept: <span class="string">&#x27;application/json, text/plain, */*&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">config2 = &#123;</span><br><span class="line">  url: <span class="string">&#x27;/config/post&#x27;</span>,</span><br><span class="line">  method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">  data: &#123;</span><br><span class="line">    a: <span class="number">1</span></span><br><span class="line">  &#125;,</span><br><span class="line">  headers: &#123;</span><br><span class="line">    test: <span class="string">&#x27;321&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">merged = &#123;</span><br><span class="line">  url: <span class="string">&#x27;/config/post&#x27;</span>,</span><br><span class="line">  method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">  data: &#123;</span><br><span class="line">    a: <span class="number">1</span></span><br><span class="line">  &#125;,</span><br><span class="line">  timeout: <span class="number">0</span>,</span><br><span class="line">  headers: &#123;</span><br><span class="line">    common: &#123;</span><br><span class="line">      Accept: <span class="string">&#x27;application/json, text/plain, */*&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">    test: <span class="string">&#x27;321&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们在 <code>core/mergeConfig.ts</code> 中实现合并方法。</p>
<h3 id="15-3-1-合并方法"><a href="#15-3-1-合并方法" class="headerlink" title="15.3.1 合并方法"></a>15.3.1 合并方法</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; AxiosRequestConfig &#125; <span class="keyword">from</span> <span class="string">&#x27;../types/index&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> strats = <span class="built_in">Object</span>.create(<span class="literal">null</span>)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">defaultStrat</span>(<span class="params">val1: <span class="built_in">any</span>, val2: <span class="built_in">any</span></span>): <span class="title">any</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">typeof</span> val2 !== <span class="string">&#x27;undefined&#x27;</span> ? val2 : val1</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="function"><span class="keyword">function</span> <span class="title">mergeConfig</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  config1: AxiosRequestConfig,</span></span></span><br><span class="line"><span class="function"><span class="params">  config2?: AxiosRequestConfig</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">AxiosRequestConfig</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (!config2) &#123;</span><br><span class="line">    config2 = &#123;&#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> config = <span class="built_in">Object</span>.create(<span class="literal">null</span>)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> key <span class="keyword">in</span> config2) &#123;</span><br><span class="line">    mergeField(key)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> key <span class="keyword">in</span> config1) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!config2[key]) &#123;</span><br><span class="line">      mergeField(key)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">mergeField</span>(<span class="params">key: <span class="built_in">string</span></span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> strat = strats[key] || defaultStrat</span><br><span class="line">    config[key] = strat(config1[key], config2![key])</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> config</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>合并方法的整体思路就是对 <code>config1</code> 和 <code>config2</code> 中的属性遍历，执行 <code>mergeField</code> 方法做合并，这里 <code>config1</code> 代表默认配置，<code>config2</code> 代表自定义配置。</p>
<p>遍历过程中，我们会通过 <code>config2[key]</code> 这种索引的方式访问，所以需要给 <code>AxiosRequestConfig</code> 的接口定义添加一个字符串索引签名：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosRequestConfig &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">  [propName: <span class="built_in">string</span>]: <span class="built_in">any</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在 <code>mergeField</code> 方法中，我们会针对不同的属性使用不同的合并策略。</p>
<h3 id="15-3-2-默认合并策略"><a href="#15-3-2-默认合并策略" class="headerlink" title="15.3.2 默认合并策略"></a>15.3.2 默认合并策略</h3><p>这是大部分属性的合并策略，如下：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">defaultStrat</span>(<span class="params">val1: <span class="built_in">any</span>, val2: <span class="built_in">any</span></span>): <span class="title">any</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">typeof</span> val2 !== <span class="string">&#x27;undefined&#x27;</span> ? val2 : val1</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>它很简单，如果有 <code>val2</code> 则返回 <code>val2</code>，否则返回 <code>val1</code>，也就是如果自定义配置中定义了某个属性，就采用自定义的，否则就用默认配置。</p>
<h3 id="15-3-3-只接受自定义配置合并策略"><a href="#15-3-3-只接受自定义配置合并策略" class="headerlink" title="15.3.3 只接受自定义配置合并策略"></a>15.3.3 只接受自定义配置合并策略</h3><p>对于一些属性如 <code>url</code>、<code>params</code>、<code>data</code>，合并策略如下：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fromVal2Strat</span>(<span class="params">val1: <span class="built_in">any</span>, val2: <span class="built_in">any</span></span>): <span class="title">any</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">typeof</span> val2 !== <span class="string">&#x27;undefined&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> val2</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> stratKeysFromVal2 = [<span class="string">&#x27;url&#x27;</span>, <span class="string">&#x27;params&#x27;</span>, <span class="string">&#x27;data&#x27;</span>]</span><br><span class="line"></span><br><span class="line">stratKeysFromVal2.forEach(<span class="function"><span class="params">key</span> =&gt;</span> &#123;</span><br><span class="line">  strats[key] = fromVal2Strat</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>因为对于 <code>url</code>、<code>params</code>、<code>data</code> 这些属性，默认配置显然是没有意义的，它们是和每个请求强相关的，所以我们只从自定义配置中获取。</p>
<h3 id="15-3-4-复杂对象合并策略"><a href="#15-3-4-复杂对象合并策略" class="headerlink" title="15.3.4 复杂对象合并策略"></a>15.3.4 复杂对象合并策略</h3><p>对于一些属性如 <code>headers</code>，合并策略如下：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">deepMergeStrat</span>(<span class="params">val1: <span class="built_in">any</span>, val2: <span class="built_in">any</span></span>): <span class="title">any</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (isPlainObject(val2)) &#123;</span><br><span class="line">    <span class="keyword">return</span> deepMerge(val1, val2)</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">typeof</span> val2 !== <span class="string">&#x27;undefined&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> val2</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (isPlainObject(val1)) &#123;</span><br><span class="line">    <span class="keyword">return</span> deepMerge(val1)</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">typeof</span> val1 !== <span class="string">&#x27;undefined&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> val1</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> stratKeysDeepMerge = [<span class="string">&#x27;headers&#x27;</span>]</span><br><span class="line"></span><br><span class="line">stratKeysDeepMerge.forEach(<span class="function"><span class="params">key</span> =&gt;</span> &#123;</span><br><span class="line">  strats[key] = deepMergeStrat</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p><code>helpers/util.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">deepMerge</span>(<span class="params">...objs: <span class="built_in">any</span>[]</span>): <span class="title">any</span> </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> result = <span class="built_in">Object</span>.create(<span class="literal">null</span>)</span><br><span class="line"></span><br><span class="line">  objs.forEach(<span class="function"><span class="params">obj</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (obj) &#123;</span><br><span class="line">      <span class="built_in">Object</span>.keys(obj).forEach(<span class="function"><span class="params">key</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> val = obj[key]</span><br><span class="line">        <span class="keyword">if</span> (isPlainObject(val)) &#123;</span><br><span class="line">          <span class="keyword">if</span> (isPlainObject(result[key])) &#123;</span><br><span class="line">            result[key] = deepMerge(result[key], val)</span><br><span class="line">          &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            result[key] = deepMerge(&#123;&#125;, val)</span><br><span class="line">          &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">          result[key] = val</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> result</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>对于 <code>headers</code> 这类的复杂对象属性，我们需要使用深拷贝的方式，同时也处理了其它一些情况，因为它们也可能是一个非对象的普通值。未来我们讲到认证授权的时候，<code>auth</code> 属性也是这个合并策略。</p>
<p>最后我们在 <code>request</code> 方法里添加合并配置的逻辑：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">config = mergeConfig(<span class="built_in">this</span>.defaults, config)</span><br></pre></td></tr></table></figure>

<h2 id="15-4-flatten-headers"><a href="#15-4-flatten-headers" class="headerlink" title="15.4 flatten headers"></a>15.4 flatten headers</h2><p>经过合并后的配置中的 <code>headers</code> 是一个复杂对象，多了 <code>common</code>、<code>post</code>、<code>get</code> 等属性，而这些属性中的值才是我们要真正添加到请求 <code>header</code> 中的。</p>
<p>举个例子：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">headers: &#123;</span><br><span class="line">  common: &#123;</span><br><span class="line">    Accept: <span class="string">&#x27;application/json, text/plain, */*&#x27;</span></span><br><span class="line">  &#125;,</span><br><span class="line">  post: &#123;</span><br><span class="line">    <span class="string">&#x27;Content-Type&#x27;</span>:<span class="string">&#x27;application/x-www-form-urlencoded&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们需要把它压成一级的，如下：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">headers: &#123;</span><br><span class="line">  Accept: <span class="string">&#x27;application/json, text/plain, */*&#x27;</span>,</span><br><span class="line"> <span class="string">&#x27;Content-Type&#x27;</span>:<span class="string">&#x27;application/x-www-form-urlencoded&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里要注意的是，对于 <code>common</code> 中定义的 <code>header</code> 字段，我们都要提取，而对于 <code>post</code>、<code>get</code> 这类提取，需要和该次请求的方法对应。</p>
<p>接下来我们实现 <code>flattenHeaders</code> 方法。</p>
<p><code>helpers/header.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">flattenHeaders</span>(<span class="params">headers: <span class="built_in">any</span>, method: Method</span>): <span class="title">any</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (!headers) &#123;</span><br><span class="line">    <span class="keyword">return</span> headers</span><br><span class="line">  &#125;</span><br><span class="line">  headers = deepMerge(headers.common || &#123;&#125;, headers[method] || &#123;&#125;, headers)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> methodsToDelete = [<span class="string">&#x27;delete&#x27;</span>, <span class="string">&#x27;get&#x27;</span>, <span class="string">&#x27;head&#x27;</span>, <span class="string">&#x27;options&#x27;</span>, <span class="string">&#x27;post&#x27;</span>, <span class="string">&#x27;put&#x27;</span>, <span class="string">&#x27;patch&#x27;</span>, <span class="string">&#x27;common&#x27;</span>]</span><br><span class="line"></span><br><span class="line">  methodsToDelete.forEach(<span class="function"><span class="params">method</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">delete</span> headers[method]</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> headers</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们可以通过 <code>deepMerge</code> 的方式把 <code>common</code>、<code>post</code> 的属性拷贝到 <code>headers</code> 这一级，然后再把 <code>common</code>、<code>post</code> 这些属性删掉。</p>
<p>然后我们在真正发送请求前执行这个逻辑。</p>
<p><code>core/dispatchRequest.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">processConfig</span>(<span class="params">config: AxiosRequestConfig</span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">  config.url = transformURL(config)</span><br><span class="line">  config.headers = transformHeaders(config)</span><br><span class="line">  config.data = transformRequestData(config)</span><br><span class="line">  config.headers = flattenHeaders(config.headers, config.method!)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这样确保我们了配置中的 <code>headers</code> 是可以正确添加到请求 <code>header</code> 中的</p>
<h2 id="15-5-demo-编写"><a href="#15-5-demo-编写" class="headerlink" title="15.5 demo 编写"></a>15.5 demo 编写</h2><p>在 <code>examples</code> 目录下创建 <code>config</code> 目录，在 <code>config</code> 目录下创建 <code>index.html</code>:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Config example<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;/__build__/config.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>接着创建 <code>app.ts</code> 作为入口文件：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;../../src/index&#x27;</span></span><br><span class="line"><span class="keyword">import</span> qs <span class="keyword">from</span> <span class="string">&#x27;qs&#x27;</span></span><br><span class="line"></span><br><span class="line">axios.defaults.headers.common[<span class="string">&#x27;test2&#x27;</span>] = <span class="number">123</span></span><br><span class="line"></span><br><span class="line">axios(&#123;</span><br><span class="line">  url: <span class="string">&#x27;/config/post&#x27;</span>,</span><br><span class="line">  method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">  data: qs.stringify(&#123;</span><br><span class="line">    a: <span class="number">1</span></span><br><span class="line">  &#125;),</span><br><span class="line">  headers: &#123;</span><br><span class="line">    test: <span class="string">&#x27;321&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;).then(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res.data)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>这个例子中我们额外引入了 <code>qs</code> 库，它是一个查询字符串解析和字符串化的库。</p>
<p>axios实例AxiosInstance需要从Axios中继承defaults属性</p>
<p> <code>type/index.ts</code> </p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> Axios &#123;</span><br><span class="line">  <span class="comment">// 默认：请求配置</span></span><br><span class="line">  defaults: AxiosRequestConfig</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>添加路由规则</p>
<p> <code>server.ts</code> </p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">router.post(<span class="string">&#x27;/config/post&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  res.json(req.body)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>比如我们的例子中对于 <code>&#123;a:1&#125;</code> 经过 <code>qs.stringify</code> 变成 <code>a=1</code>。</p>
<p>由于我们的例子给默认值添加了 <code>post</code> 和 <code>common</code> 的 <code>headers</code>，我们在请求前做配置合并，于是我们请求的 <code>header</code> 就添加了 <code>Content-Type</code> 字段，它的值是 <code>application/x-www-form-urlencoded</code>；另外我们也添加了 <code>test2</code> 字段，它的值是 <code>123</code>。</p>
<p>至此，我们合并配置的逻辑就实现完了。我们在前面的章节编写 <code>axios</code> 的基础功能的时候对请求数据和响应数据都做了处理，官方 <code>axios</code> 则把这俩部分逻辑也做到了默认配置中，意味这用户可以去修改这俩部分的逻辑，实现自己对请求和响应数据处理的逻辑。那么下一节我们就来实现这个 feature。</p>
<h1 id="16-请求和响应配置化"><a href="#16-请求和响应配置化" class="headerlink" title="16.请求和响应配置化"></a>16.请求和响应配置化</h1><h2 id="16-1-需求分析"><a href="#16-1-需求分析" class="headerlink" title="16.1 需求分析"></a>16.1 需求分析</h2><p>官方的 axios 库 给默认配置添加了 <code>transformRequest</code> 和 <code>transformResponse</code> 两个字段，它们的值是一个数组或者是一个函数。</p>
<p>其中 <code>transformRequest</code> 允许你在将请求数据发送到服务器之前对其进行修改，这只适用于请求方法 <code>put</code>、<code>post</code> 和 <code>patch</code>，如果值是数组，则数组中的最后一个函数必须返回一个字符串或 <code>FormData</code>、<code>URLSearchParams</code>、<code>Blob</code> 等类型作为 <code>xhr.send</code> 方法的参数，而且在 <code>transform</code> 过程中可以修改 <code>headers</code> 对象。</p>
<p>而 <code>transformResponse</code> 允许你在把响应数据传递给 <code>then</code> 或者 <code>catch</code> 之前对它们进行修改。</p>
<p>当值为数组的时候，数组的每一个函数都是一个转换函数，数组中的函数就像管道一样依次执行，前者的输出作为后者的输入。</p>
<p>举个例子：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">axios(&#123;</span><br><span class="line">  transformRequest: [(<span class="function"><span class="keyword">function</span>(<span class="params">data</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> qs.stringify(data)</span><br><span class="line">  &#125;), ...axios.defaults.transformRequest],</span><br><span class="line">  transformResponse: [axios.defaults.transformResponse, <span class="function"><span class="keyword">function</span>(<span class="params">data</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> data === <span class="string">&#x27;object&#x27;</span>) &#123;</span><br><span class="line">      data.b = <span class="number">2</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> data</span><br><span class="line">  &#125;],</span><br><span class="line">  url: <span class="string">&#x27;/config/post&#x27;</span>,</span><br><span class="line">  method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">  data: &#123;</span><br><span class="line">    a: <span class="number">1</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<h2 id="16-2-修改默认配置"><a href="#16-2-修改默认配置" class="headerlink" title="16.2 修改默认配置"></a>16.2 修改默认配置</h2><p>先修改 <code>AxiosRequestConfig</code> 的类型定义，添加 <code>transformRequest</code> 和 <code>transformResponse</code> 俩个可选属性。</p>
<p><code>types/index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosRequestConfig &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  transformRequest?: AxiosTransformer | AxiosTransformer[]</span><br><span class="line">  transformResponse?: AxiosTransformer | AxiosTransformer[]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosTransformer &#123;</span><br><span class="line">  (data: <span class="built_in">any</span>, headers?: <span class="built_in">any</span>): <span class="built_in">any</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>接着修改默认配置，如下：</p>
<p><code>defaults.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; processHeaders &#125; <span class="keyword">from</span> <span class="string">&#x27;./helpers/headers&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; transformRequest, transformResponse &#125; <span class="keyword">from</span> <span class="string">&#x27;./helpers/data&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> defaults: AxiosRequestConfig = &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  transformRequest: [</span><br><span class="line">    <span class="function"><span class="keyword">function</span>(<span class="params">data: <span class="built_in">any</span>, headers: <span class="built_in">any</span></span>): <span class="title">any</span> </span>&#123;</span><br><span class="line">      processHeaders(headers, data)</span><br><span class="line">      <span class="keyword">return</span> transformRequest(data)</span><br><span class="line">    &#125;</span><br><span class="line">  ],</span><br><span class="line"></span><br><span class="line">  transformResponse: [</span><br><span class="line">    <span class="function"><span class="keyword">function</span>(<span class="params">data: <span class="built_in">any</span></span>): <span class="title">any</span> </span>&#123;</span><br><span class="line">      <span class="keyword">return</span> transformResponse(data)</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们把之前对请求数据和响应数据的处理逻辑，放到了默认配置中，也就是默认处理逻辑。</p>
<h2 id="16-3-transform-逻辑重构"><a href="#16-3-transform-逻辑重构" class="headerlink" title="16.3 transform 逻辑重构"></a>16.3 transform 逻辑重构</h2><p>接下来，我们就要重构之前写的对请求数据和响应数据的处理逻辑了。由于我们可能会编写多个转换函数，我们先定义一个 <code>transform</code> 函数来处理这些转换函数的调用逻辑。</p>
<p><code>core/transform.ts</code></p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; AxiosTransformer &#125; <span class="keyword">from</span> <span class="string">&#x27;../types&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="function"><span class="keyword">function</span> <span class="title">transform</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  data: <span class="built_in">any</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">  headers: <span class="built_in">any</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">  fns?: AxiosTransformer | AxiosTransformer[]</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">any</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (!fns) &#123;</span><br><span class="line">    <span class="keyword">return</span> data</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (!<span class="built_in">Array</span>.isArray(fns)) &#123;</span><br><span class="line">    fns = [fns]</span><br><span class="line">  &#125;</span><br><span class="line">  fns.forEach(<span class="function"><span class="params">fn</span> =&gt;</span> &#123;</span><br><span class="line">    data = fn(data, headers)</span><br><span class="line">  &#125;)</span><br><span class="line">  <span class="keyword">return</span> data</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>transform</code> 函数中接收 <code>data</code>、<code>headers</code>、<code>fns</code> 3 个参数，其中 <code>fns</code> 代表一个或者多个转换函数，内部逻辑很简单，遍历 <code>fns</code>，执行这些转换函数，并且把 <code>data</code> 和 <code>headers</code> 作为参数传入，每个转换函数返回的 <code>data</code> 会作为下一个转换函数的参数 <code>data</code> 传入。</p>
<p>接下来修改对请求数据和响应数据的处理逻辑。</p>
<p><code>dispatchRequest.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> transform <span class="keyword">from</span> <span class="string">&#x27;./transform&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">processConfig</span>(<span class="params">config: AxiosRequestConfig</span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">  config.url = transformURL(config)</span><br><span class="line">  config.data = transform(config.data, config.headers, config.transformRequest)</span><br><span class="line">  config.headers = flattenHeaders(config.headers, config.method!)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">transformResponseData</span>(<span class="params">res: AxiosResponse</span>): <span class="title">AxiosResponse</span> </span>&#123;</span><br><span class="line">  res.data = transform(res.data, res.headers, res.config.transformResponse)</span><br><span class="line">  <span class="keyword">return</span> res</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们把对请求数据的处理和对响应数据的处理改成使用 <code>transform</code> 函数实现，并把配置中的 <code>transformRequest</code> 及 <code>transformResponse</code> 分别传入。</p>
<h2 id="16-4-demo-编写"><a href="#16-4-demo-编写" class="headerlink" title="16.4 demo 编写"></a>16.4 demo 编写</h2><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">axios(&#123;</span><br><span class="line">  transformRequest: [(<span class="function"><span class="keyword">function</span>(<span class="params">data</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> qs.stringify(data)</span><br><span class="line">  &#125;), ...(axios.defaults.transformRequest <span class="keyword">as</span> AxiosTransformer[])],</span><br><span class="line">  transformResponse: [...(axios.defaults.transformResponse <span class="keyword">as</span> AxiosTransformer[]), <span class="function"><span class="keyword">function</span>(<span class="params">data</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> data === <span class="string">&#x27;object&#x27;</span>) &#123;</span><br><span class="line">      data.b = <span class="number">2</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> data</span><br><span class="line">  &#125;],</span><br><span class="line">  url: <span class="string">&#x27;/config/post&#x27;</span>,</span><br><span class="line">  method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">  data: &#123;</span><br><span class="line">    a: <span class="number">1</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;).then(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res.data)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>我们对 <code>transformRequest</code> 做了修改，在执行它默认的 <code>transformRequest</code> 之前，我们先用 <code>qs.stringify</code> 库对传入的数据 <code>data</code> 做了一层转换。同时也对 <code>transformResponse</code> 做了修改，在执行完默认的 <code>transformResponse</code> 后，会给响应的 <code>data</code> 对象添加一个 <code>data.b = 2</code>。</p>
<p>因为之前我们实现了配置的合并，而且我们传入的 <code>transformRequest</code> 和 <code>transformResponse</code> 遵循默认合并策略，它们会覆盖默认的值。</p>
<p>至此，我们就实现了请求和响应的配置化。到目前为止，我们的 axios 都是一个单例，一旦我们修改了 axios 的默认配置，会影响所有的请求。官网提供了一个 <code>axios.create</code> 的工厂方法允许我们创建一个新的 <code>axios</code> 实例，同时允许我们传入新的配置和默认配置合并，并做为新的默认配置。下面一节课我们就来实现这个 feature。</p>
<h1 id="17-扩展-axios-create-静态接口"><a href="#17-扩展-axios-create-静态接口" class="headerlink" title="17.扩展 axios.create 静态接口"></a>17.扩展 axios.create 静态接口</h1><h2 id="17-1-需求分析"><a href="#17-1-需求分析" class="headerlink" title="17.1 需求分析"></a>17.1 需求分析</h2><p>目前为止，我们的 axios 都是一个单例，一旦我们修改了 axios 的默认配置，会影响所有的请求。我们希望提供了一个 <code>axios.create</code> 的静态接口允许我们创建一个新的 <code>axios</code> 实例，同时允许我们传入新的配置和默认配置合并，并做为新的默认配置。</p>
<p>举个例子：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> instance = axios.create(&#123;</span><br><span class="line">  transformRequest: [(<span class="function"><span class="keyword">function</span>(<span class="params">data</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> qs.stringify(data)</span><br><span class="line">  &#125;), ...(axios.defaults.transformRequest <span class="keyword">as</span> AxiosTransformer[])],</span><br><span class="line">  transformResponse: [...(axios.defaults.transformResponse <span class="keyword">as</span> AxiosTransformer[]), <span class="function"><span class="keyword">function</span>(<span class="params">data</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> data === <span class="string">&#x27;object&#x27;</span>) &#123;</span><br><span class="line">      data.b = <span class="number">2</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> data</span><br><span class="line">  &#125;]</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">instance(&#123;</span><br><span class="line">  url: <span class="string">&#x27;/config/post&#x27;</span>,</span><br><span class="line">  method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">  data: &#123;</span><br><span class="line">    a: <span class="number">1</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<h2 id="17-2-静态方法扩展"><a href="#17-2-静态方法扩展" class="headerlink" title="17.2 静态方法扩展"></a>17.2 静态方法扩展</h2><p>由于 <code>axios</code> 扩展了一个静态接口，因此我们先来修改接口类型定义。</p>
<p><code>types/index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosStatic <span class="keyword">extends</span> AxiosInstance&#123;</span><br><span class="line">  create(config?: AxiosRequestConfig): AxiosInstance</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>create</code> 函数可以接受一个 <code>AxiosRequestConfig</code> 类型的配置，作为默认配置的扩展，也可以接受不传参数。</p>
<p>接着我们来实现 <code>axios.create</code> 静态方法。</p>
<p><code>axios.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createInstance</span>(<span class="params">config: AxiosRequestConfig</span>): <span class="title">AxiosStatic</span> </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> context = <span class="keyword">new</span> Axios(config)</span><br><span class="line">  <span class="keyword">const</span> instance = Axios.prototype.request.bind(context)</span><br><span class="line"></span><br><span class="line">  extend(instance, context)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> instance <span class="keyword">as</span> AxiosStatic</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">axios.create = <span class="function"><span class="keyword">function</span> <span class="title">create</span>(<span class="params">config: AxiosRequestConfig</span>): <span class="title">AxiosStatic</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> createInstance(mergeConfig(defaults, config))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>内部调用了 <code>createInstance</code> 函数，并且把参数 <code>config</code> 与 <code>defaults</code> 合并，作为新的默认配置。注意这里我们需要 <code>createInstance</code> 函数的返回值类型为 <code>AxiosStatic</code>。</p>
<h2 id="17-3-demo-编写"><a href="#17-3-demo-编写" class="headerlink" title="17.3 demo 编写"></a>17.3 demo 编写</h2><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> instance = axios.create(&#123;</span><br><span class="line">  transformRequest: [(<span class="function"><span class="keyword">function</span>(<span class="params">data</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> qs.stringify(data)</span><br><span class="line">  &#125;), ...(axios.defaults.transformRequest <span class="keyword">as</span> AxiosTransformer[])],</span><br><span class="line">  transformResponse: [...(axios.defaults.transformResponse <span class="keyword">as</span> AxiosTransformer[]), <span class="function"><span class="keyword">function</span>(<span class="params">data</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> data === <span class="string">&#x27;object&#x27;</span>) &#123;</span><br><span class="line">      data.b = <span class="number">2</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> data</span><br><span class="line">  &#125;]</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">instance(&#123;</span><br><span class="line">  url: <span class="string">&#x27;/config/post&#x27;</span>,</span><br><span class="line">  method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">  data: &#123;</span><br><span class="line">    a: <span class="number">1</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;).then(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res.data)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>我们对上节课的示例做了小小的修改，通过 <code>axios.create</code> 方法创建一个新的实例 <code>instance</code>，并传入了 <code>transformRequest</code> 和 <code>transformResponse</code> 的配置修改了默认配置，然后通过 <code>instance</code> 发送请求，效果和之前是一样的。</p>
<p>至此我们实现了 <code>axios.create</code> 静态接口的扩展，整个 <code>ts-axios</code> 的配置化也告一段落。官方 axios 库还支持了对请求取消的能力，在发送请求前以及请求发送出去未响应前都可以取消该请求。下一章我们就来实现这个 feature。</p>
<h1 id="18-取消功能的设计与实现"><a href="#18-取消功能的设计与实现" class="headerlink" title="18.取消功能的设计与实现"></a>18.取消功能的设计与实现</h1><h2 id="18-1-需求分析"><a href="#18-1-需求分析" class="headerlink" title="18.1 需求分析"></a>18.1 需求分析</h2><p>有些场景下，我们希望能主动取消请求，比如常见的搜索框案例，在用户输入过程中，搜索框的内容也在不断变化，正常情况每次变化我们都应该向服务端发送一次请求。但是当用户输入过快的时候，我们不希望每次变化请求都发出去，通常一个解决方案是前端用 debounce 的方案，比如延时 200ms 发送请求。这样当用户连续输入的字符，只要输入间隔小于 200ms，前面输入的字符都不会发请求。</p>
<p>但是还有一种极端情况是后端接口很慢，比如超过 1s 才能响应，这个时候即使做了 200ms 的 debounce，但是在我慢慢输入（每个输入间隔超过 200ms）的情况下，在前面的请求没有响应前，也有可能发出去多个请求。因为接口的响应时长是不定的，如果先发出去的请求响应时长比后发出去的请求要久一些，后请求的响应先回来，先请求的响应后回来，就会出现前面请求响应结果覆盖后面请求响应结果的情况，那么就乱了。因此在这个场景下，我们除了做 debounce，还希望后面的请求发出去的时候，如果前面的请求还没有响应，我们可以把前面的请求取消。</p>
<p>从 axios 的取消接口设计层面，我们希望做如下的设计：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> CancelToken = axios.CancelToken;</span><br><span class="line"><span class="keyword">const</span> source = CancelToken.source();</span><br><span class="line"></span><br><span class="line">axios.get(<span class="string">&#x27;/user/12345&#x27;</span>, &#123;</span><br><span class="line">  cancelToken: source.token</span><br><span class="line">&#125;).catch(<span class="function"><span class="keyword">function</span> (<span class="params">e</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (axios.isCancel(e)) &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">&#x27;Request canceled&#x27;</span>, e.message);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 处理错误</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 取消请求 (请求原因是可选的)</span></span><br><span class="line">source.cancel(<span class="string">&#x27;Operation canceled by the user.&#x27;</span>);</span><br></pre></td></tr></table></figure>

<p>我们给 <code>axios</code> 添加一个 <code>CancelToken</code> 的对象，它有一个 <code>source</code> 方法可以返回一个 <code>source</code> 对象，<code>source.token</code> 是在每次请求的时候传给配置对象中的 <code>cancelToken</code> 属性，然后在请求发出去之后，我们可以通过 <code>source.cancel</code> 方法取消请求。</p>
<p>我们还支持另一种方式的调用：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> CancelToken = axios.CancelToken;</span><br><span class="line"><span class="keyword">let</span> cancel;</span><br><span class="line"></span><br><span class="line">axios.get(<span class="string">&#x27;/user/12345&#x27;</span>, &#123;</span><br><span class="line">  cancelToken: <span class="keyword">new</span> CancelToken(<span class="function"><span class="keyword">function</span> <span class="title">executor</span>(<span class="params">c</span>) </span>&#123;</span><br><span class="line">    cancel = c;</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 取消请求</span></span><br><span class="line">cancel();</span><br></pre></td></tr></table></figure>

<p><code>axios.CancelToken</code> 是一个类，我们直接把它实例化的对象传给请求配置中的 <code>cancelToken</code> 属性，<code>CancelToken</code> 的构造函数参数支持传入一个 <code>executor</code> 方法，该方法的参数是一个取消函数 <code>c</code>，我们可以在 <code>executor</code> 方法执行的内部拿到这个取消函数 <code>c</code>，赋值给我们外部定义的 <code>cancel</code> 变量，之后我们可以通过调用这个 <code>cancel</code> 方法来取消请求。</p>
<h2 id="18-2-异步分离的设计方案"><a href="#18-2-异步分离的设计方案" class="headerlink" title="18.2 异步分离的设计方案"></a>18.2 异步分离的设计方案</h2><p>通过需求分析，我们知道想要实现取消某次请求，我们需要为该请求配置一个 <code>cancelToken</code>，然后在外部调用一个 <code>cancel</code> 方法。</p>
<p>请求的发送是一个异步过程，最终会执行 <code>xhr.send</code> 方法，<code>xhr</code> 对象提供了 <a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/abort"><code>abort</code></a>方法，可以把请求取消。因为我们在外部是碰不到 <code>xhr</code> 对象的，所以我们想在执行 <code>cancel</code> 的时候，去执行 <code>xhr.abort</code> 方法。</p>
<p>现在就相当于我们在 <code>xhr</code> 异步请求过程中，插入一段代码，当我们在外部执行 <code>cancel</code> 函数的时候，会驱动这段代码的执行，然后执行 <code>xhr.abort</code> 方法取消请求。</p>
<p>我们可以利用 Promise 实现异步分离，也就是在 <code>cancelToken</code> 中保存一个 <code>pending</code> 状态的 Promise 对象，然后当我们执行 <code>cancel</code> 方法的时候，能够访问到这个 Promise 对象，把它从 <code>pending</code> 状态变成 <code>resolved</code> 状态，这样我们就可以在 <code>then</code> 函数中去实现取消请求的逻辑，类似如下的代码：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (cancelToken) &#123;</span><br><span class="line">  cancelToken.promise</span><br><span class="line">    .then(<span class="function"><span class="params">reason</span> =&gt;</span> &#123;</span><br><span class="line">      request.abort()</span><br><span class="line">      reject(reason)</span><br><span class="line">    &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="18-3-CancelToken-类实现"><a href="#18-3-CancelToken-类实现" class="headerlink" title="18.3 CancelToken 类实现"></a>18.3 CancelToken 类实现</h2><p>接下来，我们就来实现这个 <code>CancelToken</code> 类，先来看一下接口定义：</p>
<h3 id="18-3-1-接口定义"><a href="#18-3-1-接口定义" class="headerlink" title="18.3.1 接口定义"></a>18.3.1 接口定义</h3><p><code>types/index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosRequestConfig &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  cancelToken?: CancelToken</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> CancelToken &#123;</span><br><span class="line">  promise: <span class="built_in">Promise</span>&lt;<span class="built_in">string</span>&gt;</span><br><span class="line">  reason?: <span class="built_in">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> Canceler &#123;</span><br><span class="line">  (message?: <span class="built_in">string</span>): <span class="built_in">void</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> CancelExecutor &#123;</span><br><span class="line">  (cancel: Canceler): <span class="built_in">void</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>其中 <code>CancelToken</code> 是实例类型的接口定义，<code>Canceler</code> 是取消方法的接口定义，<code>CancelExecutor</code> 是 <code>CancelToken</code> 类构造函数参数的接口定义。</p>
<h3 id="18-3-2-代码实现"><a href="#18-3-2-代码实现" class="headerlink" title="18.3.2 代码实现"></a>18.3.2 代码实现</h3><p>我们单独创建 <code>cancel</code> 目录来管理取消相关的代码，在 <code>cancel</code> 目录下创建 <code>CancelToken.ts</code> 文件：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; CancelExecutor &#125; <span class="keyword">from</span> <span class="string">&#x27;../types&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> ResolvePromise &#123;</span><br><span class="line">  (reason?: <span class="built_in">string</span>): <span class="built_in">void</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="class"><span class="keyword">class</span> <span class="title">CancelToken</span> </span>&#123;</span><br><span class="line">  promise: <span class="built_in">Promise</span>&lt;<span class="built_in">string</span>&gt;</span><br><span class="line">  reason?: <span class="built_in">string</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="title">constructor</span>(<span class="params">executor: CancelExecutor</span>)</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> resolvePromise: ResolvePromise</span><br><span class="line">    <span class="built_in">this</span>.promise = <span class="keyword">new</span> <span class="built_in">Promise</span>&lt;<span class="built_in">string</span>&gt;(<span class="function"><span class="params">resolve</span> =&gt;</span> &#123;</span><br><span class="line">      resolvePromise = resolve</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    executor(<span class="function"><span class="params">message</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (<span class="built_in">this</span>.reason) &#123;</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">      &#125;</span><br><span class="line">      <span class="built_in">this</span>.reason = message</span><br><span class="line">      resolvePromise(<span class="built_in">this</span>.reason)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在 <code>CancelToken</code> 构造函数内部，实例化一个 <code>pending</code> 状态的 Promise 对象，然后用一个 <code>resolvePromise</code> 变量指向 <code>resolve</code> 函数。接着执行 <code>executor</code> 函数，传入一个 <code>cancel</code> 函数，在 <code>cancel</code> 函数内部，会调用 <code>resolvePromise</code> 把 Promise 对象从 <code>pending</code> 状态变为 <code>resolved</code> 状态。</p>
<p>接着我们在 <code>xhr.ts</code> 中插入一段取消请求的逻辑。</p>
<p><code>core/xhr.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; <span class="comment">/*....*/</span> cancelToken &#125; = config</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (cancelToken) &#123;</span><br><span class="line">  cancelToken.promise.then(<span class="function"><span class="params">reason</span> =&gt;</span> &#123;</span><br><span class="line">    request.abort()</span><br><span class="line">    reject(reason)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这样就满足了第二种使用方式，接着我们要实现第一种使用方式，给 <code>CancelToken</code> 扩展静态接口。</p>
<h2 id="18-4-CancelToken-扩展静态接口"><a href="#18-4-CancelToken-扩展静态接口" class="headerlink" title="18.4 CancelToken 扩展静态接口"></a>18.4 CancelToken 扩展静态接口</h2><h3 id="18-4-1-接口定义"><a href="#18-4-1-接口定义" class="headerlink" title="18.4.1 接口定义"></a>18.4.1 接口定义</h3><p><code>types/index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> CancelTokenSource &#123;</span><br><span class="line">  token: CancelToken</span><br><span class="line">  cancel: Canceler</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> CancelTokenStatic &#123;</span><br><span class="line">  <span class="keyword">new</span>(executor: CancelExecutor): CancelToken</span><br><span class="line"></span><br><span class="line">  source(): CancelTokenSource</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>其中 <code>CancelTokenSource</code> 作为 <code>CancelToken</code> 类静态方法 <code>source</code> 函数的返回值类型，<code>CancelTokenStatic</code> 则作为 <code>CancelToken</code> 类的类类型。</p>
<h3 id="18-4-2-代码实现"><a href="#18-4-2-代码实现" class="headerlink" title="18.4.2 代码实现"></a>18.4.2 代码实现</h3><p><code>cancel/CancelToken.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="class"><span class="keyword">class</span> <span class="title">CancelToken</span> </span>&#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">static</span> source(): CancelTokenSource &#123;</span><br><span class="line">    <span class="keyword">let</span> cancel!: Canceler</span><br><span class="line">    <span class="keyword">const</span> token = <span class="keyword">new</span> CancelToken(<span class="function"><span class="params">c</span> =&gt;</span> &#123;</span><br><span class="line">      cancel = c</span><br><span class="line">    &#125;)</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      cancel,</span><br><span class="line">      token</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>source</code> 的静态方法很简单，定义一个 <code>cancel</code> 变量实例化一个 <code>CancelToken</code> 类型的对象，然后在 <code>executor</code> 函数中，把 <code>cancel</code> 指向参数 <code>c</code> 这个取消函数。</p>
<p>这样就满足了我们第一种使用方式，但是在第一种使用方式的例子中，我们在捕获请求的时候，通过 <code>axios.isCancel</code> 来判断这个错误参数 e 是不是一次取消请求导致的错误，接下来我们对取消错误的原因做一层包装，并且把给 <code>axios</code> 扩展静态方法</p>
<h2 id="18-5-Cancel-类实现及-axios-的扩展"><a href="#18-5-Cancel-类实现及-axios-的扩展" class="headerlink" title="18.5 Cancel 类实现及 axios 的扩展"></a>18.5 Cancel 类实现及 axios 的扩展</h2><h3 id="18-5-1-接口定义"><a href="#18-5-1-接口定义" class="headerlink" title="18.5.1 接口定义"></a>18.5.1 接口定义</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> Cancel &#123;</span><br><span class="line">  message?: <span class="built_in">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> CancelStatic &#123;</span><br><span class="line">  <span class="keyword">new</span>(message?: <span class="built_in">string</span>): Cancel</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosStatic <span class="keyword">extends</span> AxiosInstance &#123;</span><br><span class="line">  create(config?: AxiosRequestConfig): AxiosInstance</span><br><span class="line"></span><br><span class="line">  CancelToken: CancelTokenStatic</span><br><span class="line">  Cancel: CancelStatic</span><br><span class="line">  isCancel: <span class="function">(<span class="params">value: <span class="built_in">any</span></span>) =&gt;</span> <span class="built_in">boolean</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>其中 <code>Cancel</code> 是实例类型的接口定义，<code>CancelStatic</code> 是类类型的接口定义，并且我们给 <code>axios</code> 扩展了多个静态方法。</p>
<h3 id="18-5-2-代码实现"><a href="#18-5-2-代码实现" class="headerlink" title="18.5.2 代码实现"></a>18.5.2 代码实现</h3><p>我在 <code>cancel</code> 目录下创建 <code>Cancel.ts</code> 文件。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="class"><span class="keyword">class</span> <span class="title">Cancel</span> </span>&#123;</span><br><span class="line">  message?: <span class="built_in">string</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="title">constructor</span>(<span class="params">message?: <span class="built_in">string</span></span>)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.message = message</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">isCancel</span>(<span class="params">value: <span class="built_in">any</span></span>): <span class="title">boolean</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> value <span class="keyword">instanceof</span> Cancel</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>Cancel</code> 类非常简单，拥有一个 <code>message</code> 的公共属性。<code>isCancel</code> 方法也非常简单，通过 <code>instanceof</code> 来判断传入的值是不是一个 <code>Cancel</code> 对象。</p>
<p>接着我们对 <code>CancelToken</code> 类中的 <code>reason</code> 类型做修改，把它变成一个 <code>Cancel</code> 类型的实例。</p>
<p>先修改定义部分。</p>
<p><code>types/index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> CancelToken &#123;</span><br><span class="line">  promise: <span class="built_in">Promise</span>&lt;Cancel&gt;</span><br><span class="line">  reason?: Cancel</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>再修改实现部分：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Cancel <span class="keyword">from</span> <span class="string">&#x27;./Cancel&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> ResolvePromise &#123;</span><br><span class="line">  (reason?: Cancel): <span class="built_in">void</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="class"><span class="keyword">class</span> <span class="title">CancelToken</span> </span>&#123;</span><br><span class="line">  promise: <span class="built_in">Promise</span>&lt;Cancel&gt;</span><br><span class="line">  reason?: Cancel</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="title">constructor</span>(<span class="params">executor: CancelExecutor</span>)</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> resolvePromise: ResolvePromise</span><br><span class="line">    <span class="built_in">this</span>.promise = <span class="keyword">new</span> <span class="built_in">Promise</span>&lt;Cancel&gt;(<span class="function"><span class="params">resolve</span> =&gt;</span> &#123;</span><br><span class="line">      resolvePromise = resolve</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    executor(<span class="function"><span class="params">message</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (<span class="built_in">this</span>.reason) &#123;</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">      &#125;</span><br><span class="line">      <span class="built_in">this</span>.reason = <span class="keyword">new</span> Cancel(message)</span><br><span class="line">      resolvePromise(<span class="built_in">this</span>.reason)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>接下来我们给 <code>axios</code> 扩展一些静态方法，供用户使用。</p>
<p><code>axios.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> CancelToken <span class="keyword">from</span> <span class="string">&#x27;./cancel/CancelToken&#x27;</span></span><br><span class="line"><span class="keyword">import</span> Cancel, &#123; isCancel &#125; <span class="keyword">from</span> <span class="string">&#x27;./cancel/Cancel&#x27;</span></span><br><span class="line"></span><br><span class="line">axios.CancelToken = CancelToken</span><br><span class="line">axios.Cancel = Cancel</span><br><span class="line">axios.isCancel = isCancel</span><br></pre></td></tr></table></figure>

<h2 id="18-6-额外逻辑实现"><a href="#18-6-额外逻辑实现" class="headerlink" title="18.6 额外逻辑实现"></a>18.6 额外逻辑实现</h2><p>除此之外，我们还需要实现一些额外逻辑，比如当一个请求携带的 <code>cancelToken</code> 已经被使用过，那么我们甚至都可以不发送这个请求，只需要抛一个异常即可，并且抛异常的信息就是我们取消的原因，所以我们需要给 <code>CancelToken</code> 扩展一个方法。</p>
<p>先修改定义部分。</p>
<p><code>types/index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> CancelToken &#123;</span><br><span class="line">  promise: <span class="built_in">Promise</span>&lt;Cancel&gt;</span><br><span class="line">  reason?: Cancel</span><br><span class="line"></span><br><span class="line">  throwIfRequested(): <span class="built_in">void</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>添加一个 <code>throwIfRequested</code> 方法，接下来实现它：</p>
<p><code>cancel/CancelToken.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="class"><span class="keyword">class</span> <span class="title">CancelToken</span> </span>&#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">  throwIfRequested(): <span class="built_in">void</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span>.reason) &#123;</span><br><span class="line">      <span class="keyword">throw</span> <span class="built_in">this</span>.reason</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>判断如果存在 <code>this.reason</code>，说明这个 <code>token</code> 已经被使用过了，直接抛错。</p>
<p>接下来在发送请求前增加一段逻辑。</p>
<p><code>core/dispatchRequest.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="function"><span class="keyword">function</span> <span class="title">dispatchRequest</span>(<span class="params">config: AxiosRequestConfig</span>): <span class="title">AxiosPromise</span> </span>&#123;</span><br><span class="line">  throwIfCancellationRequested(config)</span><br><span class="line">  processConfig(config)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">throwIfCancellationRequested</span>(<span class="params">config: AxiosRequestConfig</span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (config.cancelToken) &#123;</span><br><span class="line">    config.cancelToken.throwIfRequested()</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>发送请求前检查一下配置的 cancelToken 是否已经使用过了，如果已经被用过则不用法请求，直接抛异常。</p>
<h2 id="18-7-demo-编写"><a href="#18-7-demo-编写" class="headerlink" title="18.7 demo 编写"></a>18.7 demo 编写</h2><p>在 <code>examples</code> 目录下创建 <code>cancel</code> 目录，在 <code>cancel</code> 目录下创建 <code>index.html</code>:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Cancel example<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;/__build__/cancel.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>接着创建 <code>app.ts</code> 作为入口文件：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios, &#123; Canceler &#125; <span class="keyword">from</span> <span class="string">&#x27;../../src/index&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> CancelToken = axios.CancelToken</span><br><span class="line"><span class="keyword">const</span> source = CancelToken.source()</span><br><span class="line"></span><br><span class="line">axios.get(<span class="string">&#x27;/cancel/get&#x27;</span>, &#123;</span><br><span class="line">  cancelToken: source.token</span><br><span class="line">&#125;).catch(<span class="function"><span class="keyword">function</span>(<span class="params">e</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (axios.isCancel(e)) &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">&#x27;Request canceled&#x27;</span>, e.message)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  source.cancel(<span class="string">&#x27;Operation canceled by the user.&#x27;</span>)</span><br><span class="line"></span><br><span class="line">  axios.post(<span class="string">&#x27;/cancel/post&#x27;</span>, &#123; <span class="attr">a</span>: <span class="number">1</span> &#125;, &#123; <span class="attr">cancelToken</span>: source.token &#125;).catch(<span class="function"><span class="keyword">function</span>(<span class="params">e</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (axios.isCancel(e)) &#123;</span><br><span class="line">      <span class="built_in">console</span>.log(e.message)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;, <span class="number">100</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> cancel: Canceler</span><br><span class="line"></span><br><span class="line">axios.get(<span class="string">&#x27;/cancel/get&#x27;</span>, &#123;</span><br><span class="line">  cancelToken: <span class="keyword">new</span> CancelToken(<span class="function"><span class="params">c</span> =&gt;</span> &#123;</span><br><span class="line">    cancel = c</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;).catch(<span class="function"><span class="keyword">function</span>(<span class="params">e</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (axios.isCancel(e)) &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">&#x27;Request canceled&#x27;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  cancel()</span><br><span class="line">&#125;, <span class="number">200</span>)</span><br></pre></td></tr></table></figure>

<p>添加路由规则</p>
<p> <code>server.ts</code> </p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">router.get(<span class="string">&#x27;/cancel/get&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    res.json(<span class="string">&#x27;hello&#x27;</span>)</span><br><span class="line">  &#125;, <span class="number">1000</span>)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">router.post(<span class="string">&#x27;/cancel/post&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    res.json(req.body)</span><br><span class="line">  &#125;, <span class="number">1000</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>我们的 demo 展示了 2 种使用方式，也演示了如果一个 token 已经被使用过，则再次携带该 token 的请求并不会发送。</p>
<p>至此，我们完成了 <code>ts-axios</code> 的请求取消功能，我们巧妙地利用了 Promise 实现了异步分离。目前官方 <code>axios</code> 库的一些大的 feature 我们都已经实现了，下面的章节我们就开始补充完善 <code>ts-axios</code> 的其它功能。</p>
<h1 id="19-withCredentials"><a href="#19-withCredentials" class="headerlink" title="19.withCredentials"></a>19.withCredentials</h1><h2 id="19-1-需求分析"><a href="#19-1-需求分析" class="headerlink" title="19.1 需求分析"></a>19.1 需求分析</h2><p>有些时候我们会发一些跨域请求，比如 <code>http://domain-a.com</code> 站点发送一个 <code>http://api.domain-b.com/get</code> 的请求，默认情况下，浏览器会根据同源策略限制这种跨域请求，但是可以通过 <a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">CORS</a>技术解决跨域问题。</p>
<p>在同域的情况下，我们发送请求会默认携带当前域下的 cookie，但是在跨域的情况下，默认是不会携带请求域下的 cookie 的，比如 <code>http://domain-a.com</code> 站点发送一个 <code>http://api.domain-b.com/get</code> 的请求，默认是不会携带 <code>api.domain-b.com</code> 域下的 cookie，如果我们想携带（很多情况下是需要的），只需要设置请求的 <code>xhr</code> 对象的 <code>withCredentials</code> 为 true 即可。</p>
<h2 id="19-2-代码实现"><a href="#19-2-代码实现" class="headerlink" title="19.2 代码实现"></a>19.2 代码实现</h2><p>先修改 <code>AxiosRequestConfig</code> 的类型定义。</p>
<p><code>types/index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosRequestConfig &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  withCredentials?: <span class="built_in">boolean</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>然后修改请求发送前的逻辑。</p>
<p><code>core/xhr.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; <span class="comment">/*...*/</span> withCredentials &#125; = config</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (withCredentials) &#123;</span><br><span class="line">  request.withCredentials = <span class="literal">true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="19-3-demo-编写"><a href="#19-3-demo-编写" class="headerlink" title="19.3 demo 编写"></a>19.3 demo 编写</h2><p>在 <code>examples</code> 目录下创建 <code>more</code> 目录，在 <code>more</code> 目录下创建 <code>index.html</code>:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>More example<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;/__build__/more.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>接着创建 <code>app.ts</code> 作为入口文件：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;../../src/index&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">document</span>.cookie = <span class="string">&#x27;a=b&#x27;</span></span><br><span class="line"></span><br><span class="line">axios.get(<span class="string">&#x27;/more/get&#x27;</span>).then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">axios.post(<span class="string">&#x27;http://127.0.0.1:8088/more/server2&#x27;</span>, &#123; &#125;, &#123;</span><br><span class="line">  withCredentials: <span class="literal">true</span></span><br><span class="line">&#125;).then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>配置接口路由</p>
<p> <code>server.js</code></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">router.get(<span class="string">&#x27;/more/get&#x27;</span>, <span class="function">(<span class="params">req, res</span>) =&gt;</span> &#123;</span><br><span class="line">  res.json(req.cookies)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">router.post(<span class="string">&#x27;/more/post&#x27;</span>, <span class="function"><span class="keyword">function</span> (<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> auth = req.headers.authorization</span><br><span class="line">  <span class="keyword">const</span> [type, credentials] = auth.split(<span class="string">&#x27; &#x27;</span>)</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">&#x27;atob on server:&#x27;</span>, atob(credentials))</span><br><span class="line">  <span class="keyword">const</span> [username, password] = atob(credentials).split(<span class="string">&#x27;:&#x27;</span>).map(<span class="function"><span class="params">item</span> =&gt;</span> item.trim())</span><br><span class="line">  <span class="keyword">if</span> (type === <span class="string">&#x27;Basic&#x27;</span> &amp;&amp; username === <span class="string">&#x27;chen&#x27;</span> &amp;&amp; password === <span class="string">&#x27;123456&#x27;</span>) &#123;</span><br><span class="line">    res.json(req.body)</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    res.status(<span class="number">401</span>)</span><br><span class="line">    res.end(<span class="string">&#x27;UnAuthorization&#x27;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">router.get(<span class="string">&#x27;/more/304&#x27;</span>, <span class="function"><span class="keyword">function</span> (<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  res.status(<span class="number">304</span>)</span><br><span class="line">  res.end()</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">router.get(<span class="string">&#x27;/more/A&#x27;</span>, <span class="function"><span class="keyword">function</span> (<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  res.end(<span class="string">&#x27;A&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">router.get(<span class="string">&#x27;/more/B&#x27;</span>, <span class="function"><span class="keyword">function</span> (<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  res.end(<span class="string">&#x27;B&#x27;</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>这次我们除了给 <code>server.js</code> 去配置了接口路由，还创建了 <code>server2.js</code>，起了一个跨域的服务。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">&#x27;express&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> bodyParser = <span class="built_in">require</span>(<span class="string">&#x27;body-parser&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> cookieParser = <span class="built_in">require</span>(<span class="string">&#x27;cookie-parser&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = express()</span><br><span class="line"></span><br><span class="line">app.use(bodyParser.json())</span><br><span class="line">app.use(bodyParser.urlencoded(&#123; <span class="attr">extended</span>: <span class="literal">true</span> &#125;))</span><br><span class="line">app.use(cookieParser())</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> router = express.Router()</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> cors = &#123;</span><br><span class="line">  <span class="string">&#x27;Access-Control-Allow-Origin&#x27;</span>: <span class="string">&#x27;http://localhost:8080&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;Access-Control-Allow-Credentials&#x27;</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="string">&#x27;Access-Control-Allow-Methods&#x27;</span>: <span class="string">&#x27;POST, GET, PUT, DELETE, OPTIONS&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;Access-Control-Allow-Headers&#x27;</span>: <span class="string">&#x27;Content-Type&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">router.post(<span class="string">&#x27;/more/server2&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  res.set(cors)</span><br><span class="line">  res.json(req.cookies)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">router.options(<span class="string">&#x27;/more/server2&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  res.set(cors)</span><br><span class="line">  res.end()</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">app.use(router)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> port = <span class="number">8088</span></span><br><span class="line"><span class="built_in">module</span>.exports = app.listen(port)</span><br></pre></td></tr></table></figure>

<p>这里需要安装一下 <code>cookie-parser</code> 插件，用于请求发送的 cookie。</p>
<p> <code>server2.js</code>需要在 <code>server.js</code>中导入</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">require</span>(<span class="string">&#x27;./server2&#x27;</span>)</span><br></pre></td></tr></table></figure>

<p>通过 demo 演示我们可以发现，对于同域请求，会携带 cookie，而对于跨域请求，只有我们配置了 <code>withCredentials</code> 为 true，才会携带 cookie。</p>
<p>至此我们的 <code>withCredentials</code> feature 开发完毕，下一节课我们来实现 axios 对 XSRF 的防御功能。</p>
<h1 id="20-XSRF-防御"><a href="#20-XSRF-防御" class="headerlink" title="20.XSRF 防御"></a>20.XSRF 防御</h1><h2 id="20-1-需求分析"><a href="#20-1-需求分析" class="headerlink" title="20.1 需求分析"></a>20.1 需求分析</h2><p>XSRF 又名 <a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps/Website_security#Cross-Site_Request_Forgery_(CSRF)">CSRF</a>，跨站请求伪造，它是前端常见的一种攻击方式，我们先通过一张图来认识它的攻击手段。</p>
<p><img data-src="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20210524221312.png" alt="img"></p>
<p>CSRF 的防御手段有很多，比如验证请求的 referer，但是 referer 也是可以伪造的，所以杜绝此类攻击的一种方式是服务器端要求每次请求都包含一个 <code>token</code>，这个 <code>token</code> 不在前端生成，而是在我们每次访问站点的时候生成，并通过 <code>set-cookie</code> 的方式种到客户端，然后客户端发送请求的时候，从 <code>cookie</code> 中对应的字段读取出 <code>token</code>，然后添加到请求 <code>headers</code> 中。这样服务端就可以从请求 <code>headers</code> 中读取这个 <code>token</code> 并验证，由于这个 <code>token</code> 是很难伪造的，所以就能区分这个请求是否是用户正常发起的。</p>
<p>对于我们的 <code>ts-axios</code> 库，我们要自动把这几件事做了，每次发送请求的时候，从 <code>cookie</code> 中读取对应的 <code>token</code> 值，然后添加到请求 <code>headers</code>中。我们允许用户配置 <code>xsrfCookieName</code> 和 <code>xsrfHeaderName</code>，其中 <code>xsrfCookieName</code> 表示存储 <code>token</code> 的 <code>cookie</code> 名称，<code>xsrfHeaderName</code> 表示请求 <code>headers</code> 中 <code>token</code> 对应的 <code>header</code> 名称。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">axios.get(<span class="string">&#x27;/more/get&#x27;</span>,&#123;</span><br><span class="line">  xsrfCookieName: <span class="string">&#x27;XSRF-TOKEN&#x27;</span>, <span class="comment">// default</span></span><br><span class="line">  xsrfHeaderName: <span class="string">&#x27;X-XSRF-TOKEN&#x27;</span> <span class="comment">// default</span></span><br><span class="line">&#125;).then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>我们提供 <code>xsrfCookieName</code> 和 <code>xsrfHeaderName</code> 的默认值，当然用户也可以根据自己的需求在请求中去配置 <code>xsrfCookieName</code> 和 <code>xsrfHeaderName</code>。</p>
<h2 id="20-2-代码实现"><a href="#20-2-代码实现" class="headerlink" title="20.2 代码实现"></a>20.2 代码实现</h2><p>先修改 <code>AxiosRequestConfig</code> 的类型定义。</p>
<p><code>types/index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosRequestConfig &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  xsrfCookieName?: <span class="built_in">string</span></span><br><span class="line">  xsrfHeaderName?: <span class="built_in">string</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>然后修改默认配置。</p>
<p><code>defaults.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> defaults: AxiosRequestConfig = &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  xsrfCookieName: <span class="string">&#x27;XSRF-TOKEN&#x27;</span>,</span><br><span class="line"></span><br><span class="line">  xsrfHeaderName: <span class="string">&#x27;X-XSRF-TOKEN&#x27;</span>,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>接下来我们要做三件事：</p>
<ul>
<li>首先判断如果是配置 <code>withCredentials</code> 为 <code>true</code> 或者是同域请求，我们才会请求 <code>headers</code> 添加 <code>xsrf</code> 相关的字段。</li>
<li>如果判断成功，尝试从 cookie 中读取 <code>xsrf</code> 的 <code>token</code> 值。</li>
<li>如果能读到，则把它添加到请求 <code>headers</code> 的 <code>xsrf</code> 相关字段中。</li>
</ul>
<p>我们先来实现同域请求的判断。</p>
<p><code>helpers/url.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> URLOrigin &#123;</span><br><span class="line">  protocol: <span class="built_in">string</span></span><br><span class="line">  host: <span class="built_in">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">isURLSameOrigin</span>(<span class="params">requestURL: <span class="built_in">string</span></span>): <span class="title">boolean</span> </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> parsedOrigin = resolveURL(requestURL)</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    parsedOrigin.protocol === currentOrigin.protocol &amp;&amp; parsedOrigin.host === currentOrigin.host</span><br><span class="line">  )</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> urlParsingNode = <span class="built_in">document</span>.createElement(<span class="string">&#x27;a&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> currentOrigin = resolveURL(<span class="built_in">window</span>.location.href)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">resolveURL</span>(<span class="params">url: <span class="built_in">string</span></span>): <span class="title">URLOrigin</span> </span>&#123;</span><br><span class="line">  urlParsingNode.setAttribute(<span class="string">&#x27;href&#x27;</span>, url)</span><br><span class="line">  <span class="keyword">const</span> &#123; protocol, host &#125; = urlParsingNode</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    protocol,</span><br><span class="line">    host</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>同域名的判断主要利用了一个技巧，创建一个 a 标签的 DOM，然后设置 <code>href</code> 属性为我们传入的 <code>url</code>，然后可以获取该 DOM 的 <code>protocol</code>、<code>host</code>。当前页面的 <code>url</code> 和请求的 <code>url</code> 都通过这种方式获取，然后对比它们的 <code>protocol</code> 和 <code>host</code> 是否相同即可。</p>
<p>接着实现 cookie 的读取。</p>
<p><code>helpers/cookie.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> cookie = &#123;</span><br><span class="line">  read(name: <span class="built_in">string</span>): <span class="built_in">string</span> | <span class="literal">null</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> match = <span class="built_in">document</span>.cookie.match(<span class="keyword">new</span> <span class="built_in">RegExp</span>(<span class="string">&#x27;(^|;\\s*)(&#x27;</span> + name + <span class="string">&#x27;)=([^;]*)&#x27;</span>))</span><br><span class="line">    <span class="keyword">return</span> match ? <span class="built_in">decodeURIComponent</span>(match[<span class="number">3</span>]) : <span class="literal">null</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> cookie</span><br></pre></td></tr></table></figure>

<p><code>cookie</code> 的读取逻辑很简单，利用了正则表达式可以解析到 <code>name</code> 对应的值。</p>
<p>最后实现完整的逻辑。</p>
<p><code>core/xhr.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123;</span><br><span class="line">  <span class="comment">/*...*/</span></span><br><span class="line">  xsrfCookieName,</span><br><span class="line">  xsrfHeaderName</span><br><span class="line">&#125; = config</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> ((withCredentials || isURLSameOrigin(url!)) &amp;&amp; xsrfCookieName)&#123;</span><br><span class="line">  <span class="keyword">const</span> xsrfValue = cookie.read(xsrfCookieName)</span><br><span class="line">  <span class="keyword">if</span> (xsrfValue) &#123;</span><br><span class="line">    headers[xsrfHeaderName!] = xsrfValue</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="20-3-demo-编写"><a href="#20-3-demo-编写" class="headerlink" title="20.3 demo 编写"></a>20.3 demo 编写</h2><p><code>examples/more/app.ts</code> </p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> instance = axios.create(&#123;</span><br><span class="line">  xsrfCookieName: <span class="string">&#x27;XSRF-TOKEN-D&#x27;</span>,</span><br><span class="line">  xsrfHeaderName: <span class="string">&#x27;X-XSRF-TOKEN-D&#x27;</span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">instance.get(<span class="string">&#x27;/more/get&#x27;</span>).then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p><code>examples/server.js</code>：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">app.use(express.static(__dirname, &#123;</span><br><span class="line">  setHeaders (res) &#123;</span><br><span class="line">    res.cookie(<span class="string">&#x27;XSRF-TOKEN-D&#x27;</span>, <span class="string">&#x27;1234abc&#x27;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;))</span><br></pre></td></tr></table></figure>

<p>在访问页面的时候，服务端通过 <code>set-cookie</code> 往客户端种了 <code>key</code> 为 <code>XSRF-TOKEN</code>，值为 <code>1234abc</code> 的 <code>cookie</code>，作为 <code>xsrf</code> 的 <code>token</code> 值。</p>
<p>然后我们在前端发送请求的时候，就能从 cookie 中读出 <code>key</code> 为 <code>XSRF-TOKEN</code> 的值，然后把它添加到 <code>key</code> 为 <code>X-XSRF-TOKEN</code> 的请求 <code>headers</code> 中。</p>
<p>至此，我们实现了 XSRF 的自动防御的能力，下节课我们来实现 ts-axios 对上传和下载请求的支持。</p>
<h1 id="21-上传和下载的进度监控"><a href="#21-上传和下载的进度监控" class="headerlink" title="21.上传和下载的进度监控"></a>21.上传和下载的进度监控</h1><h2 id="21-1-需求分析"><a href="#21-1-需求分析" class="headerlink" title="21.1 需求分析"></a>21.1 需求分析</h2><p>有些时候，当我们上传文件或者是请求一个大体积数据的时候，希望知道实时的进度，甚至可以基于此做一个进度条的展示。</p>
<p>我们希望给 <code>axios</code> 的请求配置提供 <code>onDownloadProgress</code> 和 <code>onUploadProgress</code> 2 个函数属性，用户可以通过这俩函数实现对下载进度和上传进度的监控。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">axios.get(<span class="string">&#x27;/more/get&#x27;</span>,&#123;</span><br><span class="line">  <span class="function"><span class="title">onDownloadProgress</span>(<span class="params">progressEvent</span>)</span> &#123;</span><br><span class="line">    <span class="comment">// 监听下载进度</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">axios.post(<span class="string">&#x27;/more/post&#x27;</span>,&#123;</span><br><span class="line">  <span class="function"><span class="title">onUploadProgress</span>(<span class="params">progressEvent</span>)</span> &#123;</span><br><span class="line">    <span class="comment">// 监听上传进度</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p><code>xhr</code> 对象提供了一个 <a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/progress_event"><code>progress</code></a> 事件，我们可以监听此事件对数据的下载进度做监控；另外，<a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/upload"><code>xhr.uplaod</code></a> 对象也提供了 <a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/progress_event"><code>progress</code></a> 事件，我们可以基于此对上传进度做监控。</p>
<h2 id="21-2-代码实现"><a href="#21-2-代码实现" class="headerlink" title="21.2 代码实现"></a>21.2 代码实现</h2><p>首先修改一下类型定义。</p>
<p><code>types/index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosRequestConfig &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  onDownloadProgress?: <span class="function">(<span class="params">e: ProgressEvent</span>) =&gt;</span> <span class="built_in">void</span></span><br><span class="line">  onUploadProgress?: <span class="function">(<span class="params">e: ProgressEvent</span>) =&gt;</span> <span class="built_in">void</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>接着在发送请求前，给 <code>xhr</code> 对象添加属性。</p>
<p><code>core/xhr.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123;</span><br><span class="line">  <span class="comment">/*...*/</span></span><br><span class="line">  onDownloadProgress,</span><br><span class="line">  onUploadProgress</span><br><span class="line">&#125; = config</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (onDownloadProgress) &#123;</span><br><span class="line">  request.onprogress = onDownloadProgress</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (onUploadProgress) &#123;</span><br><span class="line">  request.upload.onprogress = onUploadProgress</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>另外，如果请求的数据是 <code>FormData</code> 类型，我们应该主动删除请求 <code>headers</code> 中的 <code>Content-Type</code> 字段，让浏览器自动根据请求数据设置 <code>Content-Type</code>。比如当我们通过 <code>FormData</code> 上传文件的时候，浏览器会把请求 <code>headers</code> 中的 <code>Content-Type</code> 设置为 <code>multipart/form-data</code>。</p>
<p>我们先添加一个判断 <code>FormData</code> 的方法。</p>
<p><code>helpers/util.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">isFormData</span>(<span class="params">val: <span class="built_in">any</span></span>): <span class="title">boolean</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">typeof</span> val !== <span class="string">&#x27;undefined&#x27;</span> &amp;&amp; val <span class="keyword">instanceof</span> FormData</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>然后再添加相关逻辑。</p>
<p><code>core/xhr.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (isFormData(data)) &#123;</span><br><span class="line">  <span class="keyword">delete</span> headers[<span class="string">&#x27;Content-Type&#x27;</span>]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们发现，<code>xhr</code> 函数内部随着需求越来越多，代码也越来越臃肿，我们可以把逻辑梳理一下，把内部代码做一层封装优化。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="function"><span class="keyword">function</span> <span class="title">xhr</span>(<span class="params">config: AxiosRequestConfig</span>): <span class="title">AxiosPromise</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123;</span><br><span class="line">      data = <span class="literal">null</span>,</span><br><span class="line">      url,</span><br><span class="line">      method = <span class="string">&#x27;get&#x27;</span>,</span><br><span class="line">      headers,</span><br><span class="line">      responseType,</span><br><span class="line">      timeout,</span><br><span class="line">      cancelToken,</span><br><span class="line">      withCredentials,</span><br><span class="line">      xsrfCookieName,</span><br><span class="line">      xsrfHeaderName,</span><br><span class="line">      onDownloadProgress,</span><br><span class="line">      onUploadProgress</span><br><span class="line">    &#125; = config</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> request = <span class="keyword">new</span> XMLHttpRequest()</span><br><span class="line"></span><br><span class="line">    request.open(method.toUpperCase(), url!, <span class="literal">true</span>)</span><br><span class="line"></span><br><span class="line">    configureRequest()</span><br><span class="line"></span><br><span class="line">    addEvents()</span><br><span class="line"></span><br><span class="line">    processHeaders()</span><br><span class="line"></span><br><span class="line">    processCancel()</span><br><span class="line"></span><br><span class="line">    request.send(data)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">configureRequest</span>(<span class="params"></span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">      <span class="keyword">if</span> (responseType) &#123;</span><br><span class="line">        request.responseType = responseType</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (timeout) &#123;</span><br><span class="line">        request.timeout = timeout</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (withCredentials) &#123;</span><br><span class="line">        request.withCredentials = withCredentials</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">addEvents</span>(<span class="params"></span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">      request.onreadystatechange = <span class="function"><span class="keyword">function</span> <span class="title">handleLoad</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (request.readyState !== <span class="number">4</span>) &#123;</span><br><span class="line">          <span class="keyword">return</span></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (request.status === <span class="number">0</span>) &#123;</span><br><span class="line">          <span class="keyword">return</span></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">const</span> responseHeaders = parseHeaders(request.getAllResponseHeaders())</span><br><span class="line">        <span class="keyword">const</span> responseData =</span><br><span class="line">          responseType &amp;&amp; responseType !== <span class="string">&#x27;text&#x27;</span> ? request.response : request.responseText</span><br><span class="line">        <span class="keyword">const</span> response: AxiosResponse = &#123;</span><br><span class="line">          data: responseData,</span><br><span class="line">          status: request.status,</span><br><span class="line">          statusText: request.statusText,</span><br><span class="line">          headers: responseHeaders,</span><br><span class="line">          config,</span><br><span class="line">          request</span><br><span class="line">        &#125;</span><br><span class="line">        handleResponse(response)</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      request.onerror = <span class="function"><span class="keyword">function</span> <span class="title">handleError</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">        reject(createError(<span class="string">&#x27;Network Error&#x27;</span>, config, <span class="literal">null</span>, request))</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      request.ontimeout = <span class="function"><span class="keyword">function</span> <span class="title">handleTimeout</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">        reject(</span><br><span class="line">          createError(<span class="string">`Timeout of <span class="subst">$&#123;config.timeout&#125;</span> ms exceeded`</span>, config, <span class="string">&#x27;ECONNABORTED&#x27;</span>, request)</span><br><span class="line">        )</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (onDownloadProgress) &#123;</span><br><span class="line">        request.onprogress = onDownloadProgress</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (onUploadProgress) &#123;</span><br><span class="line">        request.upload.onprogress = onUploadProgress</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">processHeaders</span>(<span class="params"></span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">      <span class="keyword">if</span> (isFormData(data)) &#123;</span><br><span class="line">        <span class="keyword">delete</span> headers[<span class="string">&#x27;Content-Type&#x27;</span>]</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> ((withCredentials || isURLSameOrigin(url!)) &amp;&amp; xsrfCookieName) &#123;</span><br><span class="line">        <span class="keyword">const</span> xsrfValue = cookie.read(xsrfCookieName)</span><br><span class="line">        <span class="keyword">if</span> (xsrfValue) &#123;</span><br><span class="line">          headers[xsrfHeaderName!] = xsrfValue</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="built_in">Object</span>.keys(headers).forEach(<span class="function"><span class="params">name</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (data === <span class="literal">null</span> &amp;&amp; name.toLowerCase() === <span class="string">&#x27;content-type&#x27;</span>) &#123;</span><br><span class="line">          <span class="keyword">delete</span> headers[name]</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">          request.setRequestHeader(name, headers[name])</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">processCancel</span>(<span class="params"></span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">      <span class="keyword">if</span> (cancelToken) &#123;</span><br><span class="line">        cancelToken.promise.then(<span class="function"><span class="params">reason</span> =&gt;</span> &#123;</span><br><span class="line">          request.abort()</span><br><span class="line">          reject(reason)</span><br><span class="line">        &#125;)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">handleResponse</span>(<span class="params">response: AxiosResponse</span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">      <span class="keyword">if</span> (response.status &gt;= <span class="number">200</span> &amp;&amp; response.status &lt; <span class="number">300</span>) &#123;</span><br><span class="line">        resolve(response)</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        reject(</span><br><span class="line">          createError(</span><br><span class="line">            <span class="string">`Request failed with status code <span class="subst">$&#123;response.status&#125;</span>`</span>,</span><br><span class="line">            config,</span><br><span class="line">            <span class="literal">null</span>,</span><br><span class="line">            request,</span><br><span class="line">            response</span><br><span class="line">          )</span><br><span class="line">        )</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们把整个流程分为 7 步：</p>
<ul>
<li>创建一个 <code>request</code> 实例。</li>
<li>执行 <code>request.open</code> 方法初始化。</li>
<li>执行 <code>configureRequest</code> 配置 <code>request</code> 对象。</li>
<li>执行 <code>addEvents</code> 给 <code>request</code> 添加事件处理函数。</li>
<li>执行 <code>processHeaders</code> 处理请求 <code>headers</code>。</li>
<li>执行 <code>processCancel</code> 处理请求取消逻辑。</li>
<li>执行 <code>request.send</code> 方法发送请求。</li>
</ul>
<p>这样拆分后整个流程就会显得非常清晰，未来我们再去新增需求的时候代码也不会显得越来越臃肿。</p>
<h2 id="21-3-demo-编写"><a href="#21-3-demo-编写" class="headerlink" title="21.3 demo 编写"></a>21.3 demo 编写</h2><p>这节课的 demo 非常有意思，我们第一次给界面上增加了一些交互的按钮。在这里，与原文不同，我选择创建了一个新的<code>upload-download</code>文件夹进行操作</p>
<p><code>examples/upload-download/index.html</code></p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">title</span>&gt;</span>More example<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;stylesheet&quot;</span> <span class="attr">type</span>=<span class="string">&quot;text/css&quot;</span> <span class="attr">href</span>=<span class="string">&quot;//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>file download<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">button</span> <span class="attr">id</span>=<span class="string">&quot;download&quot;</span> <span class="attr">class</span>=<span class="string">&quot;btn btn-primary&quot;</span>&gt;</span>Download<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>file upload<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">form</span> <span class="attr">role</span>=<span class="string">&quot;form&quot;</span> <span class="attr">class</span>=<span class="string">&quot;form&quot;</span> <span class="attr">onsubmit</span>=<span class="string">&quot;return false;&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">input</span> <span class="attr">id</span>=<span class="string">&quot;file&quot;</span> <span class="attr">type</span>=<span class="string">&quot;file&quot;</span> <span class="attr">class</span>=<span class="string">&quot;form-control&quot;</span>/&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">button</span> <span class="attr">id</span>=<span class="string">&quot;upload&quot;</span> <span class="attr">type</span>=<span class="string">&quot;button&quot;</span> <span class="attr">class</span>=<span class="string">&quot;btn btn-primary&quot;</span>&gt;</span>Upload<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">form</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;/__build__/upload-download.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>另外，我们为了友好地展示上传和下载进度，我们引入了一个开源库 <a target="_blank" rel="noopener" href="https://github.com/rstacruz/nprogress">nprogress</a>，它可以在页面的顶部展示进度条。</p>
<p><code>examples/upload-download/app.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;../../src/index&#x27;</span></span><br><span class="line"><span class="keyword">import</span> <span class="string">&#x27;nprogress/nprogress.css&#x27;</span></span><br><span class="line"><span class="keyword">import</span> NProgress <span class="keyword">from</span> <span class="string">&#x27;nprogress&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> instance = axios.create(&#123;&#125;)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">calculatePercentage</span>(<span class="params">loaded: <span class="built_in">number</span>, total: <span class="built_in">number</span></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">Math</span>.floor(loaded * <span class="number">1.0</span>) / total</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">loadProgressBar</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> setupStartProgress = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    instance.interceptors.request.use(<span class="function"><span class="params">config</span> =&gt;</span> &#123;</span><br><span class="line">      NProgress.start()</span><br><span class="line">      <span class="keyword">return</span> config</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> setupUpdateProgress = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> update = <span class="function">(<span class="params">e: ProgressEvent</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="built_in">console</span>.log(e)</span><br><span class="line">      NProgress.set(calculatePercentage(e.loaded, e.total))</span><br><span class="line">    &#125;</span><br><span class="line">    instance.defaults.onDownloadProgress = update</span><br><span class="line">    instance.defaults.onUploadProgress = update</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> setupStopProgress = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    instance.interceptors.response.use(</span><br><span class="line">      response =&gt; &#123;</span><br><span class="line">        NProgress.done()</span><br><span class="line">        <span class="keyword">return</span> response</span><br><span class="line">      &#125;,</span><br><span class="line">      error =&gt; &#123;</span><br><span class="line">        NProgress.done()</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">Promise</span>.reject(error)</span><br><span class="line">      &#125;</span><br><span class="line">    )</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  setupStartProgress()</span><br><span class="line">  setupUpdateProgress()</span><br><span class="line">  setupStopProgress()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">loadProgressBar()</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> downloadEl = <span class="built_in">document</span>.getElementById(<span class="string">&#x27;download&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> downloadFileURL = <span class="string">&#x27;https://img.mukewang.com/5cc01a7b0001a33718720632.jpg&#x27;</span></span><br><span class="line"></span><br><span class="line">downloadEl.addEventListener(<span class="string">&#x27;click&#x27;</span>, <span class="function"><span class="params">e</span> =&gt;</span> &#123;</span><br><span class="line">  instance.get(downloadFileURL).then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(</span><br><span class="line">      <span class="string">`download file success, data.length: <span class="subst">$&#123;res.data.length&#125;</span>, data.url: <span class="subst">$&#123;res.config.url&#125;</span>`</span></span><br><span class="line">    )</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> uploadEl = <span class="built_in">document</span>.getElementById(<span class="string">&#x27;upload&#x27;</span>)</span><br><span class="line"></span><br><span class="line">uploadEl!.addEventListener(<span class="string">&#x27;click&#x27;</span>, <span class="function"><span class="params">e</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> data = <span class="keyword">new</span> FormData()</span><br><span class="line">  <span class="keyword">const</span> fileEl = <span class="built_in">document</span>.getElementById(<span class="string">&#x27;file&#x27;</span>) <span class="keyword">as</span> HTMLInputElement</span><br><span class="line">  <span class="keyword">if</span> (fileEl.files) &#123;</span><br><span class="line">    data.append(<span class="string">&#x27;file&#x27;</span>, fileEl.files[<span class="number">0</span>])</span><br><span class="line"></span><br><span class="line">    instance.post(<span class="string">&#x27;/more/upload&#x27;</span>, data).then(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">&#x27;upload file success, you can see it on ./exapmles/accept-upload-file&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>对于 <code>progress</code> 事件参数 <code>e</code>，会有 <code>e.total</code> 和 <code>e.loaded</code> 属性，表示进程总体的工作量和已经执行的工作量，我们可以根据这 2 个值算出当前进度，然后通过 <code>Nprogess.set</code> 设置。另外，我们通过配置请求拦截器和响应拦截器执行 <code>NProgress.start()</code> 和 <code>NProgress.done()</code>。</p>
<p>我们给下载按钮绑定了一个 <code>click</code> 事件，请求一张图片，我们可以看到实时的进度；另外我们也给上传按钮绑定了一个 <code>click</code> 事件，上传我们选择的文件，同样也能看到实时进度。</p>
<p>在服务端，我们为了处理上传请求，需要下载安装一个 <code>express</code> 的中间件 <code>connect-multiparty</code>，然后使用它。</p>
<p><code>example/server.js</code>：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> multipart = <span class="built_in">require</span>(<span class="string">&#x27;connect-multiparty&#x27;</span>)</span><br><span class="line"></span><br><span class="line">app.use(mutipart(&#123;</span><br><span class="line">  uploadDir: path.resolve(__dirname, <span class="string">&#x27;upload-file&#x27;</span>)</span><br><span class="line">&#125;))</span><br><span class="line"></span><br><span class="line">router.post(<span class="string">&#x27;/more/upload&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(req.body, req.files)</span><br><span class="line">  res.end(<span class="string">&#x27;upload success!&#x27;</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>这里我们需要在 <code>examples</code> 目录下创建一个 <code>upload-file</code> 的空目录，用于存放上传的文件。</p>
<p>通过这个中间件，我们就可以处理上传请求并且可以把上传的文件存储在 <code>upload-file</code> 目录下。</p>
<p>为了保证代码正常运行，我们还需要在 <code>examples/webpack.config.js</code> 中添加 <code>css-loader</code> 和 <code>css-loader</code>，不要忘记先安装它们。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">rules: [</span><br><span class="line">  &#123;</span><br><span class="line">    test: <span class="regexp">/\.css$/</span>,</span><br><span class="line">    use: [<span class="string">&#x27;style-loader&#x27;</span>, <span class="string">&#x27;css-loader&#x27;</span>]</span><br><span class="line">  &#125;</span><br><span class="line">]</span><br></pre></td></tr></table></figure>

<p>至此，<code>ts-axios</code> 支持了上传下载进度事件的回调函数的配置，用户可以通过配置这俩函数实现对下载进度和上传进度的监控。下一节课我们来实现 http 的认证授权功能。</p>
<h1 id="22-HTTP-授权"><a href="#22-HTTP-授权" class="headerlink" title="22.HTTP 授权"></a>22.HTTP 授权</h1><h2 id="22-1-需求分析"><a href="#22-1-需求分析" class="headerlink" title="22.1 需求分析"></a>22.1 需求分析</h2><p>HTTP 协议中的 <a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization">Authorization</a> 请求 header 会包含服务器用于验证用户代理身份的凭证，通常会在服务器返回 401 Unauthorized 状态码以及 WWW-Authenticate 消息头之后在后续请求中发送此消息头。</p>
<p>axios 库也允许你在请求配置中配置 <code>auth</code> 属性，<code>auth</code> 是一个对象结构，包含 <code>username</code> 和 <code>password</code> 2 个属性。一旦用户在请求的时候配置这俩属性，我们就会自动往 HTTP 的 请求 header 中添加 <code>Authorization</code> 属性，它的值为 <code>Basic 加密串</code>。<br>这里的加密串是 <code>username:password</code> base64 加密后的结果。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">axios.post(<span class="string">&#x27;/more/post&#x27;</span>, &#123;</span><br><span class="line">  a: <span class="number">1</span></span><br><span class="line">&#125;, &#123;</span><br><span class="line">  auth: &#123;</span><br><span class="line">    username: <span class="string">&#x27;Yee&#x27;</span>,</span><br><span class="line">    password: <span class="string">&#x27;123456&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;).then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<h2 id="22-2-代码实现"><a href="#22-2-代码实现" class="headerlink" title="22.2 代码实现"></a>22.2 代码实现</h2><p>首先修改一下类型定义。</p>
<p><code>types/index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosRequestConfig &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  auth?: AxiosBasicCredentials</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosBasicCredentials &#123;</span><br><span class="line">  username: <span class="built_in">string</span></span><br><span class="line">  password: <span class="built_in">string</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>接着修改合并规则，因为 auth 也是一个对象格式，所以它的合并规则是 <code>deepMergeStrat</code>。</p>
<p><code>core/mergeConfig.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> stratKeysDeepMerge = [<span class="string">&#x27;headers&#x27;</span>, <span class="string">&#x27;auth&#x27;</span>]</span><br></pre></td></tr></table></figure>

<p>然后修改发送请求前的逻辑。</p>
<p><code>core/xhr.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123;</span><br><span class="line">  <span class="comment">/*...*/</span></span><br><span class="line">  auth</span><br><span class="line">&#125; = config</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (auth) &#123;</span><br><span class="line">  headers[<span class="string">&#x27;Authorization&#x27;</span>] = <span class="string">&#x27;Basic &#x27;</span> + btoa(auth.username + <span class="string">&#x27;:&#x27;</span> + auth.password)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="22-3-demo-编写"><a href="#22-3-demo-编写" class="headerlink" title="22.3 demo 编写"></a>22.3 demo 编写</h2><p><code>examples/more/app.ts</code> </p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">axios.post(<span class="string">&#x27;/more/post&#x27;</span>, &#123;</span><br><span class="line">  a: <span class="number">1</span></span><br><span class="line">&#125;, &#123;</span><br><span class="line">  auth: &#123;</span><br><span class="line">    username: <span class="string">&#x27;Yee&#x27;</span>,</span><br><span class="line">    password: <span class="string">&#x27;123456&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;).then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>另外，我们在 <code>server.js</code> 中对于这个路由接口写了一段小逻辑：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">router.post(<span class="string">&#x27;/more/post&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> auth = req.headers.authorization</span><br><span class="line">  <span class="keyword">const</span> [type, credentials] = auth.split(<span class="string">&#x27; &#x27;</span>)</span><br><span class="line">  <span class="built_in">console</span>.log(atob(credentials))</span><br><span class="line">  <span class="keyword">const</span> [username, password] = atob(credentials).split(<span class="string">&#x27;:&#x27;</span>)</span><br><span class="line">  <span class="keyword">if</span> (type === <span class="string">&#x27;Basic&#x27;</span> &amp;&amp; username === <span class="string">&#x27;Yee&#x27;</span> &amp;&amp; password === <span class="string">&#x27;123456&#x27;</span>) &#123;</span><br><span class="line">    res.json(req.body)</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    res.end(<span class="string">&#x27;UnAuthorization&#x27;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>注意，这里我们需要安装第三方库 <code>atob</code> 实现 base64 串的解码。</p>
<p>至此，<code>ts-axios</code> 支持了 HTTP 授权功能，用户可以通过配置 auth 对象实现自动在请求 header 中添加 <code>Authorization</code> 属性。下一节课我们来实现自定义合法状态码功能。</p>
<h1 id="23-自定义合法状态码"><a href="#23-自定义合法状态码" class="headerlink" title="23.自定义合法状态码"></a>23.自定义合法状态码</h1><h2 id="23-1-需求分析"><a href="#23-1-需求分析" class="headerlink" title="23.1 需求分析"></a>23.1 需求分析</h2><p>之前 <code>ts-axios</code> 在处理响应结果的时候，认为 HTTP <a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/status">status</a>在 200 和 300 之间是一个合法值，在这个区间之外则创建一个错误。有些时候我们想自定义这个规则，比如认为 304 也是一个合法的状态码，所以我们希望 <code>ts-axios</code> 能提供一个配置，允许我们自定义合法状态码规则。如下：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">axios.get(<span class="string">&#x27;/more/304&#x27;</span>, &#123;</span><br><span class="line">  <span class="function"><span class="title">validateStatus</span>(<span class="params">status</span>)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> status &gt;= <span class="number">200</span> &amp;&amp; status &lt; <span class="number">400</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;).then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res)</span><br><span class="line">&#125;).catch(<span class="function">(<span class="params">e: AxiosError</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(e.message)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>通过在请求配置中配置一个 <code>validateStatus</code> 函数，它可以根据参数 <code>status</code> 来自定义合法状态码的规则。</p>
<h2 id="23-2-代码实现"><a href="#23-2-代码实现" class="headerlink" title="23.2 代码实现"></a>23.2 代码实现</h2><p>首先修改一下类型定义。</p>
<p><code>types/index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosRequestConfig &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  validateStatus?: <span class="function">(<span class="params">status: <span class="built_in">number</span></span>) =&gt;</span> <span class="built_in">boolean</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>然后我们来修改默认配置规则。</p>
<p><code>defaults.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">validateStatus(status: <span class="built_in">number</span>): <span class="built_in">boolean</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> status &gt;= <span class="number">200</span> &amp;&amp; status &lt; <span class="number">300</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>添加默认合法状态码的校验规则。然后再请求后对响应数据的处理逻辑。</p>
<p><code>core/xhr.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123;</span><br><span class="line">  <span class="comment">/*...*/</span></span><br><span class="line">  validateStatus</span><br><span class="line">&#125; = config</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">handleResponse</span>(<span class="params">response: AxiosResponse</span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (!validateStatus || validateStatus(response.status)) &#123;</span><br><span class="line">    resolve(response)</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    reject(</span><br><span class="line">      createError(</span><br><span class="line">        <span class="string">`Request failed with status code <span class="subst">$&#123;response.status&#125;</span>`</span>,</span><br><span class="line">        config,</span><br><span class="line">        <span class="literal">null</span>,</span><br><span class="line">        request,</span><br><span class="line">        response</span><br><span class="line">      )</span><br><span class="line">    )</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>如果没有配置 <code>validateStatus</code> 以及 <code>validateStatus</code> 函数返回的值为 true 的时候，都认为是合法的，正常 <code>resolve(response)</code>，否则都创建一个错误。</p>
<h2 id="23-3-demo-编写"><a href="#23-3-demo-编写" class="headerlink" title="23.3 demo 编写"></a>23.3 demo 编写</h2><p><code>examples/more/app.ts</code> </p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">axios.get(<span class="string">&#x27;/more/304&#x27;</span>).then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res)</span><br><span class="line">&#125;).catch(<span class="function">(<span class="params">e: AxiosError</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(e.message)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">axios.get(<span class="string">&#x27;/more/304&#x27;</span>, &#123;</span><br><span class="line">  <span class="function"><span class="title">validateStatus</span>(<span class="params">status</span>)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> status &gt;= <span class="number">200</span> &amp;&amp; status &lt; <span class="number">400</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;).then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res)</span><br><span class="line">&#125;).catch(<span class="function">(<span class="params">e: AxiosError</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(e.message)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p><code>server.js</code> 中我们编写了这个路由接口</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">router.get(<span class="string">&#x27;/more/304&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  res.status(<span class="number">304</span>)</span><br><span class="line">  res.end()</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>接口返回 304 状态码，对于默认的请求我们会输出一条错误信息。第二个请求中我们配置了自定义合法状态码规则，区间在 200 和 400 之间，这样就不会报错，而是可以正常输出响应对象。</p>
<p>至此 <code>ts-axios</code> 实现了自定义合法状态码功能，用户可以配置 <code>validateStatus</code> 自定义合法状态码规则。之前有同学会质疑 <code>ts-axios</code> 对于请求 <code>url</code> 参数的序列化处理规则，下一节课我们来实现自定义参数序列化规则功能。</p>
<h1 id="24-自定义参数序列化"><a href="#24-自定义参数序列化" class="headerlink" title="24.自定义参数序列化"></a>24.自定义参数序列化</h1><h2 id="24-1-需求分析"><a href="#24-1-需求分析" class="headerlink" title="24.1 需求分析"></a>24.1 需求分析</h2><p>在之前的章节，我们对请求的 url 参数做了处理，我们会解析传入的 params 对象，根据一定的规则把它解析成字符串，然后添加在 url 后面。在解析的过程中，我们会对字符串 encode，但是对于一些特殊字符比如 <code>@</code>、<code>+</code> 等却不转义，这是 axios 库的默认解析规则。当然，我们也希望自己定义解析规则，于是我们希望 <code>ts-axios</code> 能在请求配置中允许我们配置一个 <code>paramsSerializer</code> 函数来自定义参数的解析规则，该函数接受 <code>params</code> 参数，返回值作为解析后的结果，如下：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">axios.get(<span class="string">&#x27;/more/get&#x27;</span>, &#123;</span><br><span class="line">  params: &#123;</span><br><span class="line">    a: <span class="number">1</span>,</span><br><span class="line">    b: <span class="number">2</span>,</span><br><span class="line">    c: [<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;c&#x27;</span>]</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="function"><span class="title">paramsSerializer</span>(<span class="params">params</span>)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> qs.stringify(params, &#123; <span class="attr">arrayFormat</span>: <span class="string">&#x27;brackets&#x27;</span> &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;).then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<h2 id="24-2-代码实现"><a href="#24-2-代码实现" class="headerlink" title="24.2 代码实现"></a>24.2 代码实现</h2><p>首先修改一下类型定义。</p>
<p><code>types/index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosRequestConfig &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  paramsSerializer?: <span class="function">(<span class="params">params: <span class="built_in">any</span></span>) =&gt;</span> <span class="built_in">string</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>然后修改 <code>buildURL</code> 函数的实现。</p>
<p><code>helpers/url.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">buildURL</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  url: <span class="built_in">string</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">  params?: <span class="built_in">any</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">  paramsSerializer?: (params: <span class="built_in">any</span>) =&gt; <span class="built_in">string</span></span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">string</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (!params) &#123;</span><br><span class="line">    <span class="keyword">return</span> url</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> serializedParams</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (paramsSerializer) &#123;</span><br><span class="line">    serializedParams = paramsSerializer(params)</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (isURLSearchParams(params)) &#123;</span><br><span class="line">    serializedParams = params.toString()</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> parts: <span class="built_in">string</span>[] = []</span><br><span class="line"></span><br><span class="line">    <span class="built_in">Object</span>.keys(params).forEach(<span class="function"><span class="params">key</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> val = params[key]</span><br><span class="line">      <span class="keyword">if</span> (val === <span class="literal">null</span> || <span class="keyword">typeof</span> val === <span class="string">&#x27;undefined&#x27;</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">let</span> values = []</span><br><span class="line">      <span class="keyword">if</span> (<span class="built_in">Array</span>.isArray(val)) &#123;</span><br><span class="line">        values = val</span><br><span class="line">        key += <span class="string">&#x27;[]&#x27;</span></span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        values = [val]</span><br><span class="line">      &#125;</span><br><span class="line">      values.forEach(<span class="function"><span class="params">val</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (isDate(val)) &#123;</span><br><span class="line">          val = val.toISOString()</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (isPlainObject(val)) &#123;</span><br><span class="line">          val = <span class="built_in">JSON</span>.stringify(val)</span><br><span class="line">        &#125;</span><br><span class="line">        parts.push(<span class="string">`<span class="subst">$&#123;encode(key)&#125;</span>=<span class="subst">$&#123;encode(val)&#125;</span>`</span>)</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    serializedParams = parts.join(<span class="string">&#x27;&amp;&#x27;</span>)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (serializedParams) &#123;</span><br><span class="line">    <span class="keyword">const</span> markIndex = url.indexOf(<span class="string">&#x27;#&#x27;</span>)</span><br><span class="line">    <span class="keyword">if</span> (markIndex !== -<span class="number">1</span>) &#123;</span><br><span class="line">      url = url.slice(<span class="number">0</span>, markIndex)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    url += (url.indexOf(<span class="string">&#x27;?&#x27;</span>) === -<span class="number">1</span> ? <span class="string">&#x27;?&#x27;</span> : <span class="string">&#x27;&amp;&#x27;</span>) + serializedParams</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> url</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里我们给 <code>buildURL</code> 函数新增了 <code>paramsSerializer</code> 可选参数，另外我们还新增了对 <code>params</code> 类型判断，如果它是一个 <code>URLSearchParams</code> 对象实例的话，我们直接返回它 <code>toString</code> 后的结果。</p>
<p><code>helpers/util.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">isURLSearchParams</span>(<span class="params">val: <span class="built_in">any</span></span>): <span class="title">val</span> <span class="title">is</span> <span class="title">URLSearchParams</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">typeof</span> val !== <span class="string">&#x27;undefined&#x27;</span> &amp;&amp; val <span class="keyword">instanceof</span> URLSearchParams</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>最后我们要修改 <code>buildURL</code> 调用的逻辑。</p>
<p><code>core/dispatchRequest.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">transformURL</span>(<span class="params">config: AxiosRequestConfig</span>): <span class="title">string</span> </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; url, params, paramsSerializer &#125; = config</span><br><span class="line">  <span class="keyword">return</span> buildURL(url!, params, paramsSerializer)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="24-3-demo-编写"><a href="#24-3-demo-编写" class="headerlink" title="24.3 demo 编写"></a>24.3 demo 编写</h2><p><code>examples/more/app.ts</code> </p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">axios.get(<span class="string">&#x27;/more/get&#x27;</span>, &#123;</span><br><span class="line">  params: <span class="keyword">new</span> URLSearchParams(<span class="string">&#x27;a=b&amp;c=d&#x27;</span>)</span><br><span class="line">&#125;).then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">axios.get(<span class="string">&#x27;/more/get&#x27;</span>, &#123;</span><br><span class="line">  params: &#123;</span><br><span class="line">    a: <span class="number">1</span>,</span><br><span class="line">    b: <span class="number">2</span>,</span><br><span class="line">    c: [<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;c&#x27;</span>]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;).then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> instance = axios.create(&#123;</span><br><span class="line">  <span class="function"><span class="title">paramsSerializer</span>(<span class="params">params</span>)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> qs.stringify(params, &#123; <span class="attr">arrayFormat</span>: <span class="string">&#x27;brackets&#x27;</span> &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">instance.get(<span class="string">&#x27;/more/get&#x27;</span>, &#123;</span><br><span class="line">  params: &#123;</span><br><span class="line">    a: <span class="number">1</span>,</span><br><span class="line">    b: <span class="number">2</span>,</span><br><span class="line">    c: [<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;c&#x27;</span>]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;).then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(res)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>我们编写了 3 种情况的请求，第一种满足请求的 params 参数是 <code>URLSearchParams</code> 对象类型的。后两种请求的结果主要区别在于前者并没有对 <code>[]</code> 转义，而后者会转义。</p>
<p>至此，<code>ts-axios</code> 实现了自定义参数序列化功能，用户可以配置 <code>paramsSerializer</code> 自定义参数序列化规则。下一节课我们来实现 <code>ts-axios</code> 对 <code>baseURL</code> 的支持。</p>
<h1 id="25-baseURL"><a href="#25-baseURL" class="headerlink" title="25.baseURL"></a>25.baseURL</h1><h2 id="25-1-需求分析"><a href="#25-1-需求分析" class="headerlink" title="25.1 需求分析"></a>25.1 需求分析</h2><p>有些时候，我们会请求某个域名下的多个接口，我们不希望每次发送请求都填写完整的 url，希望可以配置一个 <code>baseURL</code>，之后都可以传相对路径。如下：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> instance = axios.create(&#123;</span><br><span class="line">  baseURL: <span class="string">&#x27;https://some-domain.com/api&#x27;</span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">instance.get(<span class="string">&#x27;/get&#x27;</span>)</span><br><span class="line"></span><br><span class="line">instance.post(<span class="string">&#x27;/post&#x27;</span>)</span><br></pre></td></tr></table></figure>

<p>我们一旦配置了 <code>baseURL</code>，之后请求传入的 <code>url</code> 都会和我们的 <code>baseURL</code> 拼接成完整的绝对地址，除非请求传入的 <code>url</code> 已经是绝对地址。</p>
<h2 id="25-2-代码实现"><a href="#25-2-代码实现" class="headerlink" title="25.2 代码实现"></a>25.2 代码实现</h2><p>首先修改一下类型定义。</p>
<p><code>types/index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosRequestConfig &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  baseURL?: <span class="built_in">string</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>接下来实现 2 个辅助函数。</p>
<p><code>helpers/url.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">isAbsoluteURL</span>(<span class="params">url: <span class="built_in">string</span></span>): <span class="title">boolean</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="regexp">/^([a-z][a-z\d\+\-\.]*:)?\/\//i</span>.test(url)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">combineURL</span>(<span class="params">baseURL: <span class="built_in">string</span>, relativeURL?: <span class="built_in">string</span></span>): <span class="title">string</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> relativeURL ? baseURL.replace(<span class="regexp">/\/+$/</span>, <span class="string">&#x27;&#x27;</span>) + <span class="string">&#x27;/&#x27;</span> + relativeURL.replace(<span class="regexp">/^\/+/</span>, <span class="string">&#x27;&#x27;</span>) : baseURL</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>最后我们来调用这俩个辅助函数。</p>
<p><code>core/dispatchRequest.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">transformURL</span>(<span class="params">config: AxiosRequestConfig</span>): <span class="title">string</span> </span>&#123;</span><br><span class="line">  <span class="keyword">let</span> &#123; url, params, paramsSerializer, baseURL &#125; = config</span><br><span class="line">  <span class="keyword">if</span> (baseURL &amp;&amp; !isAbsoluteURL(url!)) &#123;</span><br><span class="line">    url = combineURL(baseURL, url)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> buildURL(url!, params, paramsSerializer)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="25-3-demo-编写"><a href="#25-3-demo-编写" class="headerlink" title="25.3 demo 编写"></a>25.3 demo 编写</h2><p><code>examples/more/app.ts</code> </p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> instance = axios.create(&#123;</span><br><span class="line">  baseURL: <span class="string">&#x27;https://img.mukewang.com/&#x27;</span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">instance.get(<span class="string">&#x27;5cc01a7b0001a33718720632.jpg&#x27;</span>)</span><br><span class="line"></span><br><span class="line">instance.get(<span class="string">&#x27;https://img.mukewang.com/szimg/5becd5ad0001b89306000338-360-202.jpg&#x27;</span>)</span><br></pre></td></tr></table></figure>

<p>这个 demo 非常简单，我们请求了慕课网的 2 张图片，注意当第二个请求 <code>url</code> 已经是绝对地址的时候，我们并不会再去拼接 <code>baseURL</code>。</p>
<p>至此，<code>ts-axios</code> 就实现了 <code>baseURL</code> 的配置功能，接下来我们来实现 <code>ts-axios</code> 的静态方法扩展。</p>
<h1 id="26-静态方法扩展"><a href="#26-静态方法扩展" class="headerlink" title="26.静态方法扩展"></a>26.静态方法扩展</h1><h2 id="26-1-需求分析"><a href="#26-1-需求分析" class="headerlink" title="26.1 需求分析"></a>26.1 需求分析</h2><p>官方 axios 库实现了 <code>axios.all</code>、<code>axios.spread</code> 等方法，它们的用法如下：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getUserAccount</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> axios.get(<span class="string">&#x27;/user/12345&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getUserPermissions</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> axios.get(<span class="string">&#x27;/user/12345/permissions&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">axios.all([getUserAccount(), getUserPermissions()])</span><br><span class="line">  .then(axios.spread(<span class="function"><span class="keyword">function</span> (<span class="params">acct, perms</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// Both requests are now complete</span></span><br><span class="line">  &#125;));</span><br></pre></td></tr></table></figure>

<p>实际上，<code>axios.all</code> 就是 <code>Promise.all</code> 的封装，它返回的是一个 <code>Promise</code> 数组，<code>then</code> 函数的参数本应是一个参数为 <code>Promise resolves</code>（数组）的函数，在这里使用了 <code>axios.spread</code> 方法。所以 <code>axios.spread</code> 方法是接收一个函数，返回一个新的函数，新函数的结构满足 <code>then</code> 函数的参数结构。</p>
<p>个人认为 <code>axios</code> 这俩静态方法在目前看来很鸡肋，因为使用 <code>Promise</code> 一样可以完成这俩需求。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getUserAccount</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> axios.get(<span class="string">&#x27;/user/12345&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getUserPermissions</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> axios.get(<span class="string">&#x27;/user/12345/permissions&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">Promise</span>.all([getUserAccount(), getUserPermissions()])</span><br><span class="line">  .then(([acct,perms]) &#123;</span><br><span class="line">    <span class="comment">// Both requests are now complete</span></span><br><span class="line">  &#125;));</span><br></pre></td></tr></table></figure>

<p>在 <code>Promise.all</code> 的 <code>resolve</code> 函数中，我们可以直接通过数组的解构拿到每个请求对应的响应对象。</p>
<p>但是为了保持与官网 axios API 一致，我们也在 <code>ts-axios</code> 库中实现这俩方法。</p>
<p>官方 axios 库也通过 <code>axios.Axios</code> 对外暴露了 <code>Axios</code> 类(感觉也没有啥使用场景，但为了保持一致，我们也会实现)。</p>
<p>另外对于 axios 实例，官网还提供了 <code>getUri</code> 方法在不发送请求的前提下根据传入的配置返回一个 url，如下：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fakeConfig = &#123;</span><br><span class="line">  baseURL: <span class="string">&#x27;https://www.baidu.com/&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/user/12345&#x27;</span>,</span><br><span class="line">  params: &#123;</span><br><span class="line">    idClient: <span class="number">1</span>,</span><br><span class="line">    idTest: <span class="number">2</span>,</span><br><span class="line">    testString: <span class="string">&#x27;thisIsATest&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">console</span>.log(axios.getUri(fakeConfig))</span><br><span class="line"><span class="comment">// https://www.baidu.com/user/12345?idClient=1&amp;idTest=2&amp;testString=thisIsATest</span></span><br></pre></td></tr></table></figure>

<h2 id="26-2-代码实现"><a href="#26-2-代码实现" class="headerlink" title="26.2 代码实现"></a>26.2 代码实现</h2><p>首先修改类型定义。</p>
<p><code>types/index.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosClassStatic &#123;</span><br><span class="line">  <span class="keyword">new</span> (config: AxiosRequestConfig): Axios</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> AxiosStatic <span class="keyword">extends</span> AxiosInstance &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">  all&lt;T&gt;(promises: <span class="built_in">Array</span>&lt;T | <span class="built_in">Promise</span>&lt;T&gt;&gt;): <span class="built_in">Promise</span>&lt;T[]&gt;</span><br><span class="line"></span><br><span class="line">  spread&lt;T, R&gt;(callback: <span class="function">(<span class="params">...args: T[]</span>) =&gt;</span> R): <span class="function">(<span class="params">arr: T[]</span>) =&gt;</span> R</span><br><span class="line"></span><br><span class="line">  Axios: AxiosClassStatic</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> Axios &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">  getUri(config?: AxiosRequestConfig): <span class="built_in">string</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>然后我们去实现这几个静态方法。</p>
<p><code>axios.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">axios.all = <span class="function"><span class="keyword">function</span> <span class="title">all</span>(<span class="params">promises</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">Promise</span>.all(promises)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">axios.spread = <span class="function"><span class="keyword">function</span> <span class="title">spread</span>(<span class="params">callback</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> <span class="title">wrap</span>(<span class="params">arr</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> callback.apply(<span class="literal">null</span>, arr)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">axios.Axios = Axios</span><br></pre></td></tr></table></figure>

<p>最后我们去给 Axios 添加实例方法 <code>getUri</code>。</p>
<p><code>core/Axios.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">getUri(config?: AxiosRequestConfig): <span class="built_in">string</span> &#123;</span><br><span class="line">  config = mergeConfig(<span class="built_in">this</span>.defaults, config)</span><br><span class="line">  <span class="keyword">return</span> transformURL(config)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>先和默认配置合并，然后再通过 <code>dispatchRequest</code> 中实现的 <code>transformURL</code> 返回一个新的 <code>url</code>。</p>
<h2 id="26-3-demo-编写"><a href="#26-3-demo-编写" class="headerlink" title="26.3 demo 编写"></a>26.3 demo 编写</h2><p><code>examples/more/app.ts</code> </p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getA</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> axios.get(<span class="string">&#x27;/more/A&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getB</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> axios.get(<span class="string">&#x27;/more/B&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">axios.all([getA(), getB()])</span><br><span class="line">  .then(axios.spread(<span class="function"><span class="keyword">function</span>(<span class="params">resA, resB</span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(resA.data)</span><br><span class="line">    <span class="built_in">console</span>.log(resB.data)</span><br><span class="line">  &#125;))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">axios.all([getA(), getB()])</span><br><span class="line">  .then(<span class="function">(<span class="params">[resA, resB]</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(resA.data)</span><br><span class="line">    <span class="built_in">console</span>.log(resB.data)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> fakeConfig = &#123;</span><br><span class="line">  baseURL: <span class="string">&#x27;https://www.baidu.com/&#x27;</span>,</span><br><span class="line">  url: <span class="string">&#x27;/user/12345&#x27;</span>,</span><br><span class="line">  params: &#123;</span><br><span class="line">    idClient: <span class="number">1</span>,</span><br><span class="line">    idTest: <span class="number">2</span>,</span><br><span class="line">    testString: <span class="string">&#x27;thisIsATest&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">console</span>.log(axios.getUri(fakeConfig))</span><br></pre></td></tr></table></figure>

<p>这里我们通过 <code>axios.all</code> 同时发出了 2 个请求，返回了 <code>Promise</code> 数组，，我们可以在 <code>axios.spread</code> 的参数函数中拿到结果，也可以直接在 then 函数的参数函数中拿到结果。另外，我们可以根据 <code>axios.getUri</code> 方法在不发送请求的情况下根据配置得到最终请求的 url 结果。</p>
<p>至此，<code>ts-axios</code> 就实现了官网 axios 库在浏览器端的所有需求。如果你学到了这里，先为自己鼓个掌吧，因为我们已经获得了阶段性的学习成果了。</p>
<p>目前为止，我们对于所写代码的验证都是通过 demo 的方式，但是 demo 毕竟难以覆盖所有场景和代码分支，为了保证代码的正确性，我们还需要更科学的方式。从下一章开始，我们会学习编写单元测试，通过单元测试的方式来保证我们的代码正确性。</p>
<h1 id="27-单元测试前言"><a href="#27-单元测试前言" class="headerlink" title="27.单元测试前言"></a>27.单元测试前言</h1><p>单元测试是前端一个很重要的方向，鉴别一个开源库是否靠谱的一个标准是它的单元测试是否完善。有了完整的单元测试，未来你去重构现有代码或者是增加新的需求都会有十足的把握不出现 regression bug。</p>
<p>在前面的章节，我们已经编写完成 ts-axios 库的代码，并通过 demo 的形式简单地对一些功能做了验证，但是 demo 可以走到的代码分支，覆盖的场景都是极其有限的。为了用更科学的手段保证我们代码的可靠性，我们需要去编写单元测试，并尽可能达到 99% 以上的测试覆盖率。</p>
<p>这门课我们会使用开源测试框架 <a target="_blank" rel="noopener" href="https://jestjs.io/en/">Jest</a>，它是 Facebook 出品的一个测试框架，相对其他测试框架，它的一大特点就是内置了常用的测试工具，比如自带断言、测试覆盖率工具，实现了开箱即用。</p>
<p>由于时间有限，我不会带大家一行行手敲测试代码，但我会把所有的知识点和测试代码都带大家过一遍，确保大家都能够学会。但是我希望你们在学习的过程中，能自己手敲这些测试代码，这样有助于你们学习和巩固。</p>
<p>通过这一章节的学习，我希望你们能够学会使用 Jest 去对 JS 库或者是 TS 库编写单元测试，并能把所学应用到你们的实际项目中。给自己的代码添加完整的测试代码也是一个非常好的开发习惯，虽然枯燥但十分实用，如果养成这些好习惯会有助于提升你的行业竞争力，所以希望大家虽然把代码实现了，也不要太骄傲，耐心把单元测试写好。</p>
<p>那么接下来就让我们开启单元测试之旅。</p>
<h1 id="28-Jest-安装和配置"><a href="#28-Jest-安装和配置" class="headerlink" title="28.Jest 安装和配置"></a>28.Jest 安装和配置</h1><h2 id="28-1-Jest-安装"><a href="#28-1-Jest-安装" class="headerlink" title="28.1 Jest 安装"></a>28.1 Jest 安装</h2><p>由于我们的项目是使用 <code>typescript-library-starter</code> 初始化的，已经内置了 Jest 的安装，但是安装的版本却不是最新的，我们可以对 <code>package.json</code> 中的相关依赖版本做修改，重新安装。</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">&quot;@types/jest&quot;</span>: <span class="string">&quot;^25.1.3&quot;</span>,</span><br><span class="line">  <span class="attr">&quot;jest&quot;</span>: <span class="string">&quot;^25.1.0&quot;</span>,</span><br><span class="line">  <span class="attr">&quot;jest-config&quot;</span>: <span class="string">&quot;^25.1.0&quot;</span>,</span><br><span class="line">  <span class="attr">&quot;ts-jest&quot;</span>: <span class="string">&quot;^25.2.1&quot;</span>,</span><br><span class="line">  <span class="attr">&quot;typescript&quot;</span>: <span class="string">&quot;^3.4.5&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>注意，这里都是目前最新的版本，未来如果有版本升级的话，可以自行更新到最新版本。</p>
</blockquote>
<p>更改版本后，在命令行再次执行 <code>npm install</code> 即可安装到相应版本。</p>
<h2 id="28-2-Jest-配置"><a href="#28-2-Jest-配置" class="headerlink" title="28.2 Jest 配置"></a>28.2 Jest 配置</h2><p>在 <code>package.json</code> 文件中有 <code>jest</code> 字段，对应 Jest 配置：</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">&quot;jest&quot;: &#123;</span><br><span class="line">  &quot;transform&quot;: &#123;</span><br><span class="line">    &quot;.(ts|tsx)&quot;: &quot;ts-jest&quot;</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;testEnvironment&quot;: &quot;jsdom&quot;,</span><br><span class="line">  &quot;testRegex&quot;: &quot;(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$&quot;,</span><br><span class="line">  &quot;moduleFileExtensions&quot;: [</span><br><span class="line">    &quot;ts&quot;,</span><br><span class="line">    &quot;tsx&quot;,</span><br><span class="line">    <span class="string">&quot;js&quot;</span></span><br><span class="line">  ],</span><br><span class="line">  &quot;coverageThreshold&quot;: &#123;</span><br><span class="line">    &quot;global&quot;: &#123;</span><br><span class="line">      &quot;branches&quot;: 90,</span><br><span class="line">      &quot;functions&quot;: 95,</span><br><span class="line">      &quot;lines&quot;: 95,</span><br><span class="line">      &quot;statements&quot;: 95</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;collectCoverageFrom&quot;: [</span><br><span class="line">    &quot;src/*.&#123;js,ts&#125;&quot;,</span><br><span class="line">    <span class="string">&quot;src/**/*.&#123;js,ts&#125;&quot;</span></span><br><span class="line">  ],</span><br><span class="line">  &quot;setupFilesAfterEnv&quot;: [</span><br><span class="line">    <span class="string">&quot;&lt;rootDir&gt;/test/boot.ts&quot;</span></span><br><span class="line">  ]</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure>

<p>接下来，我们就分别来看这几个配置的含义。</p>
<ul>
<li><a target="_blank" rel="noopener" href="https://jestjs.io/docs/en/configuration#transform-object-string-string">transform</a></li>
</ul>
<p>简单地说就是一种转换器配置，比如我们这里的 </p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&quot;transform&quot;: &#123;</span><br><span class="line">  &quot;.(ts|tsx)&quot;: &quot;ts-jest&quot;</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure>

<p>表示的就是使用 <code>ts-jest</code> 工具把 <code>.ts</code> 和 <code>.tsx</code> 文件内容转换成 JavaScript，因为我们也是使用 TypeScript 编写测试代码，而 Node.js 是不能直接支持 TypeScript 的，所以需要配置转换器。</p>
<ul>
<li><a target="_blank" rel="noopener" href="https://jestjs.io/docs/en/configuration#testenvironment-string">testEnvironment</a></li>
</ul>
<p>测试环境。</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&quot;testEnvironment&quot;: &quot;jsdom&quot;</span><br></pre></td></tr></table></figure>

<p>表示它是一个类浏览器的测试环境，我们可以使用浏览器环境中的一些 API。</p>
<ul>
<li><a target="_blank" rel="noopener" href="https://jestjs.io/docs/en/configuration#testregex-string-array-string">testRegex</a></li>
</ul>
<p>要测试文件的正则表达式。</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&quot;testRegex&quot;: &quot;(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$&quot;,</span><br></pre></td></tr></table></figure>

<p>表示 <code>tests</code> 目录下所有以 <code>.test.ts</code> 和 <code>.spec.ts</code> 的文件都需要跑测试。</p>
<ul>
<li><a target="_blank" rel="noopener" href="https://jestjs.io/docs/en/configuration#modulefileextensions-array-string">moduleFileExtensions</a></li>
</ul>
<p>模块文件扩展名，当你去引入一个模块并没有指定扩展名的时候，它会依次尝试去添加这些扩展名去找你引入的模块文件。</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&quot;moduleFileExtensions&quot;: [</span><br><span class="line">  &quot;ts&quot;,</span><br><span class="line">  &quot;tsx&quot;,</span><br><span class="line">  <span class="string">&quot;js&quot;</span></span><br><span class="line">]</span><br></pre></td></tr></table></figure>

<p>表示优先找 <code>.ts</code> 的模块、然后是 <code>.tsx</code>，最后是 <code>.js</code>。</p>
<ul>
<li><a target="_blank" rel="noopener" href="https://jestjs.io/docs/en/configuration#coveragethreshold-object">coverageThreshold</a></li>
</ul>
<p>测试覆盖率的阈值设定，当我们的测试覆盖率达不到阈值的时候，测试会失败。</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&quot;coverageThreshold&quot;: &#123;</span><br><span class="line">  &quot;global&quot;: &#123;</span><br><span class="line">    &quot;branches&quot;: 90,</span><br><span class="line">    &quot;functions&quot;: 95,</span><br><span class="line">    &quot;lines&quot;: 95,</span><br><span class="line">    &quot;statements&quot;: 95</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>表示全局的代码分支覆盖率要达到 <code>90%</code>，方法覆盖率要达到 <code>95%</code>，代码行数覆盖率达到 <code>95%</code>，声明覆盖率达到 <code>95%</code>。</p>
<ul>
<li><a target="_blank" rel="noopener" href="https://jestjs.io/docs/en/configuration#collectcoveragefrom-array">collectCoverageFrom</a></li>
</ul>
<p>收集指定文件的测试覆盖率(即使你没为这些文件编写测试)，它的值为 <a target="_blank" rel="noopener" href="https://github.com/jonschlinkert/micromatch">glob patterns</a> 类型。</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&quot;collectCoverageFrom&quot;: [</span><br><span class="line">  &quot;src/*.&#123;js,ts&#125;&quot;,</span><br><span class="line">  <span class="string">&quot;src/**/*.&#123;js,ts&#125;&quot;</span></span><br><span class="line">]</span><br></pre></td></tr></table></figure>

<p>表示收集 <code>src</code> 目录以及它的所有子目录中的 <code>js</code> 和 <code>ts</code> 文件的测试覆盖率。</p>
<ul>
<li><a target="_blank" rel="noopener" href="https://jestjs.io/docs/en/configuration#setupfilesafterenv-array">setupFilesAfterEnv</a></li>
</ul>
<p>测试框架安装后立即执行的代码文件列表。</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&quot;setupFilesAfterEnv&quot;: [</span><br><span class="line">  <span class="string">&quot;&lt;rootDir&gt;/test/boot.ts&quot;</span></span><br><span class="line">]</span><br></pre></td></tr></table></figure>

<p>表示每次跑具体测试代码之前会先运行 <code>&lt;rootDir&gt;/test/boot.ts</code> 中的代码，<code>&lt;rootDir&gt;</code> 表示当前项目的根目录。这个配置在之后的章节我们会具体介绍。</p>
<p>其他关于 Jest 的配置，感兴趣的同学可以去<a target="_blank" rel="noopener" href="https://jestjs.io/docs/en/configuration">官网</a>做扩展学习。</p>
<p>至此，我们学习了 Jest 的安装和配置，下节课我们就开始编写 <code>ts-axios</code> 库的单元测试。</p>
<h1 id="29-辅助模块单元测试"><a href="#29-辅助模块单元测试" class="headerlink" title="29.辅助模块单元测试"></a>29.辅助模块单元测试</h1><h2 id="29-1-准备工作"><a href="#29-1-准备工作" class="headerlink" title="29.1 准备工作"></a>29.1 准备工作</h2><p>通常我们会优先为一个库的辅助方法编写测试，我们会优先为 <code>ts-axios</code> 库的 <code>helpers</code> 目录下的模块编写测试。我们在 <code>test</code> 目录下创建一个 <code>helpers</code> 目录，创建一个 <code>boot.ts</code> 空文件，这个是因为我们上节课给 Jest 配置了 <code>setupFilesAfterEnv</code> 指向了这个文件，后面的章节我们会编写这个文件。</p>
<p>然后我们可以在控制台运行 <code>npm test</code>，它实际上是执行了 <code>jest --coverage</code> 来跑单元测试，我们会发现它会报错，没有匹配的测试文件，那是因为我们还没有在 <code>test</code> 目录下编写任何一个 .spec.ts 结尾的测试文件。接下来我们就来为这些辅助模块编写相应的测试。</p>
<h2 id="29-2-util-模块测试"><a href="#29-2-util-模块测试" class="headerlink" title="29.2 util 模块测试"></a>29.2 util 模块测试</h2><p><code>test/helpers/util.spec.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123;</span><br><span class="line">  isDate,</span><br><span class="line">  isPlainObject,</span><br><span class="line">  isFormData,</span><br><span class="line">  isURLSearchParams,</span><br><span class="line">  extend,</span><br><span class="line">  deepMerge</span><br><span class="line">&#125; <span class="keyword">from</span> <span class="string">&#x27;../../src/helpers/util&#x27;</span></span><br><span class="line"></span><br><span class="line">describe(<span class="string">&#x27;helpers:util&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  describe(<span class="string">&#x27;isXX&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    test(<span class="string">&#x27;should validate Date&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(isDate(<span class="keyword">new</span> <span class="built_in">Date</span>())).toBeTruthy()</span><br><span class="line">      expect(isDate(<span class="built_in">Date</span>.now())).toBeFalsy()</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should validate PlainObject&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(isPlainObject(&#123;&#125;)).toBeTruthy()</span><br><span class="line">      expect(isPlainObject(<span class="keyword">new</span> <span class="built_in">Date</span>())).toBeFalsy()</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should validate FormData&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(isFormData(<span class="keyword">new</span> FormData())).toBeTruthy()</span><br><span class="line">      expect(isFormData(&#123;&#125;)).toBeFalsy()</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should validate URLSearchParams&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(isURLSearchParams(<span class="keyword">new</span> URLSearchParams())).toBeTruthy()</span><br><span class="line">      expect(isURLSearchParams(<span class="string">&#x27;foo=1&amp;bar=2&#x27;</span>)).toBeFalsy()</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  describe(<span class="string">&#x27;extend&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    test(<span class="string">&#x27;should be mutable&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> a = <span class="built_in">Object</span>.create(<span class="literal">null</span>)</span><br><span class="line">      <span class="keyword">const</span> b = &#123; <span class="attr">foo</span>: <span class="number">123</span> &#125;</span><br><span class="line"></span><br><span class="line">      extend(a, b)</span><br><span class="line"></span><br><span class="line">      expect(a.foo).toBe(<span class="number">123</span>)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should extend properties&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">      <span class="keyword">const</span> a = &#123; <span class="attr">foo</span>: <span class="number">123</span>, <span class="attr">bar</span>: <span class="number">456</span> &#125;</span><br><span class="line">      <span class="keyword">const</span> b = &#123; <span class="attr">bar</span>: <span class="number">789</span> &#125;</span><br><span class="line">      <span class="keyword">const</span> c = extend(a, b)</span><br><span class="line"></span><br><span class="line">      expect(c.foo).toBe(<span class="number">123</span>)</span><br><span class="line">      expect(c.bar).toBe(<span class="number">789</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  describe(<span class="string">&#x27;deepMerge&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    test(<span class="string">&#x27;should be immutable&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> a = <span class="built_in">Object</span>.create(<span class="literal">null</span>)</span><br><span class="line">      <span class="keyword">const</span> b: <span class="built_in">any</span> = &#123; <span class="attr">foo</span>: <span class="number">123</span> &#125;</span><br><span class="line">      <span class="keyword">const</span> c: <span class="built_in">any</span> = &#123; <span class="attr">bar</span>: <span class="number">456</span> &#125;</span><br><span class="line"></span><br><span class="line">      deepMerge(a, b, c)</span><br><span class="line"></span><br><span class="line">      expect(<span class="keyword">typeof</span> a.foo).toBe(<span class="string">&#x27;undefined&#x27;</span>)</span><br><span class="line">      expect(<span class="keyword">typeof</span> a.bar).toBe(<span class="string">&#x27;undefined&#x27;</span>)</span><br><span class="line">      expect(<span class="keyword">typeof</span> b.bar).toBe(<span class="string">&#x27;undefined&#x27;</span>)</span><br><span class="line">      expect(<span class="keyword">typeof</span> c.foo).toBe(<span class="string">&#x27;undefined&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should deepMerge properties&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> a = &#123; <span class="attr">foo</span>: <span class="number">123</span> &#125;</span><br><span class="line">      <span class="keyword">const</span> b = &#123; <span class="attr">bar</span>: <span class="number">456</span> &#125;</span><br><span class="line">      <span class="keyword">const</span> c = &#123; <span class="attr">foo</span>: <span class="number">789</span> &#125;</span><br><span class="line">      <span class="keyword">const</span> d = deepMerge(a, b, c)</span><br><span class="line"></span><br><span class="line">      expect(d.foo).toBe(<span class="number">789</span>)</span><br><span class="line">      expect(d.bar).toBe(<span class="number">456</span>)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should deepMerge recursively&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">      <span class="keyword">const</span> a = &#123; <span class="attr">foo</span>: &#123; <span class="attr">bar</span>: <span class="number">123</span> &#125; &#125;</span><br><span class="line">      <span class="keyword">const</span> b = &#123; <span class="attr">foo</span>: &#123; <span class="attr">baz</span>: <span class="number">456</span> &#125;, <span class="attr">bar</span>: &#123; <span class="attr">qux</span>: <span class="number">789</span> &#125; &#125;</span><br><span class="line">      <span class="keyword">const</span> c = deepMerge(a, b)</span><br><span class="line"></span><br><span class="line">      expect(c).toEqual(&#123;</span><br><span class="line">        foo: &#123;</span><br><span class="line">          bar: <span class="number">123</span>,</span><br><span class="line">          baz: <span class="number">456</span></span><br><span class="line">        &#125;,</span><br><span class="line">        bar: &#123;</span><br><span class="line">          qux: <span class="number">789</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should remove all references from nested objects&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> a = &#123; <span class="attr">foo</span>: &#123; <span class="attr">bar</span>: <span class="number">123</span> &#125; &#125;</span><br><span class="line">      <span class="keyword">const</span> b = &#123;&#125;</span><br><span class="line">      <span class="keyword">const</span> c = deepMerge(a, b)</span><br><span class="line"></span><br><span class="line">      expect(c).toEqual(&#123;</span><br><span class="line">        foo: &#123;</span><br><span class="line">          bar: <span class="number">123</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;)</span><br><span class="line"></span><br><span class="line">      expect(c.foo).not.toBe(a.foo)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should handle null and undefined arguments&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(deepMerge(<span class="literal">undefined</span>, <span class="literal">undefined</span>)).toEqual(&#123;&#125;)</span><br><span class="line">      expect(deepMerge(<span class="literal">undefined</span>, &#123; <span class="attr">foo</span>: <span class="number">123</span> &#125;)).toEqual(&#123; <span class="attr">foo</span>: <span class="number">123</span> &#125;)</span><br><span class="line">      expect(deepMerge(&#123; <span class="attr">foo</span>: <span class="number">123</span> &#125;, <span class="literal">undefined</span>)).toEqual(&#123; <span class="attr">foo</span>: <span class="number">123</span> &#125;)</span><br><span class="line"></span><br><span class="line">      expect(deepMerge(<span class="literal">null</span>, <span class="literal">null</span>)).toEqual(&#123;&#125;)</span><br><span class="line">      expect(deepMerge(<span class="literal">null</span>, &#123; <span class="attr">foo</span>: <span class="number">123</span> &#125;)).toEqual(&#123; <span class="attr">foo</span>: <span class="number">123</span> &#125;)</span><br><span class="line">      expect(deepMerge(&#123; <span class="attr">foo</span>: <span class="number">123</span> &#125;, <span class="literal">null</span>)).toEqual(&#123; <span class="attr">foo</span>: <span class="number">123</span> &#125;)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>其中 <a target="_blank" rel="noopener" href="https://jestjs.io/docs/en/api#describename-fn"><code>describe</code></a> 方法用来定义一组测试，它可以支持嵌套，<a target="_blank" rel="noopener" href="https://jestjs.io/docs/en/api#testname-fn-timeout"><code>test</code></a> 函数是用来定义单个测试用例，它是测试的最小单元。<a target="_blank" rel="noopener" href="https://jestjs.io/docs/en/expect#expectvalue"><code>expect</code></a> 是断言函数，所谓”断言”，就是判断代码的实际执行结果与预期结果是否一致，如果不一致就抛出一个错误。</p>
<p>测试文件编写好后，我们可以去控制台运行一次 <code>npm test</code>，看一下测试结果，我们可以看跑了几个测试文件，测试是否通过，测试覆盖率等。</p>
<h2 id="29-3-cookie-模块测试"><a href="#29-3-cookie-模块测试" class="headerlink" title="29.3 cookie 模块测试"></a>29.3 cookie 模块测试</h2><p><code>test/helpers/cookie.spec.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cookie <span class="keyword">from</span> <span class="string">&#x27;../../src/helpers/cookie&#x27;</span></span><br><span class="line"></span><br><span class="line">describe(<span class="string">&#x27;helpers:cookie&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  test(<span class="string">&#x27;should read cookies&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">document</span>.cookie = <span class="string">&#x27;foo=baz&#x27;</span></span><br><span class="line">    expect(cookie.read(<span class="string">&#x27;foo&#x27;</span>)).toBe(<span class="string">&#x27;baz&#x27;</span>)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should return null if cookie name is not exist&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">document</span>.cookie = <span class="string">&#x27;foo=baz&#x27;</span></span><br><span class="line">    expect(cookie.read(<span class="string">&#x27;bar&#x27;</span>)).toBeNull()</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>这里我们可以通过 <code>document.cookie</code> 去设置 cookie，就像在浏览器里一样操作。</p>
<h2 id="29-4-data-模块测试"><a href="#29-4-data-模块测试" class="headerlink" title="29.4 data 模块测试"></a>29.4 data 模块测试</h2><p><code>test/helpers/data.spec.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; transformRequest, transformResponse &#125; <span class="keyword">from</span> <span class="string">&#x27;../../src/helpers/data&#x27;</span></span><br><span class="line"></span><br><span class="line">describe(<span class="string">&#x27;helpers:data&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  describe(<span class="string">&#x27;transformRequest&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    test(<span class="string">&#x27;should transform request data to string if data is a PlainObject&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> a = &#123; <span class="attr">a</span>: <span class="number">1</span> &#125;</span><br><span class="line">      expect(transformRequest(a)).toBe(<span class="string">&#x27;&#123;&quot;a&quot;:1&#125;&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should do nothing if data is not a PlainObject&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> a = <span class="keyword">new</span> URLSearchParams(<span class="string">&#x27;a=b&#x27;</span>)</span><br><span class="line">      expect(transformRequest(a)).toBe(a)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  describe(<span class="string">&#x27;transformResponse&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    test(<span class="string">&#x27;should transform response data to Object if data is a JSON string&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> a = <span class="string">&#x27;&#123;&quot;a&quot;: 2&#125;&#x27;</span></span><br><span class="line">      expect(transformResponse(a)).toEqual(&#123; <span class="attr">a</span>: <span class="number">2</span> &#125;)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should do nothing if data is a string but not a JSON string&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> a = <span class="string">&#x27;&#123;a: 2&#125;&#x27;</span></span><br><span class="line">      expect(transformResponse(a)).toBe(<span class="string">&#x27;&#123;a: 2&#125;&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should do nothing if data is not a string&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> a = &#123; <span class="attr">a</span>: <span class="number">2</span> &#125;</span><br><span class="line">      expect(transformResponse(a)).toBe(a)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<h2 id="29-5-error-模块测试"><a href="#29-5-error-模块测试" class="headerlink" title="29.5 error 模块测试"></a>29.5 error 模块测试</h2><p><code>test/helpers/error.spec.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; createError &#125; <span class="keyword">from</span> <span class="string">&#x27;../../src/helpers/error&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; AxiosRequestConfig, AxiosResponse &#125; <span class="keyword">from</span> <span class="string">&#x27;../../src/types&#x27;</span></span><br><span class="line"></span><br><span class="line">describe(<span class="string">&#x27;helpers::error&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  test(<span class="string">&#x27;should create an Error with message, config, code, request, response and isAxiosError&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> request = <span class="keyword">new</span> XMLHttpRequest()</span><br><span class="line">    <span class="keyword">const</span> config: AxiosRequestConfig = &#123; <span class="attr">method</span>: <span class="string">&#x27;post&#x27;</span> &#125;</span><br><span class="line">    <span class="keyword">const</span> response: AxiosResponse = &#123;</span><br><span class="line">      status: <span class="number">200</span>,</span><br><span class="line">      statusText: <span class="string">&#x27;OK&#x27;</span>,</span><br><span class="line">      headers: <span class="literal">null</span>,</span><br><span class="line">      request,</span><br><span class="line">      config,</span><br><span class="line">      data: &#123; <span class="attr">foo</span>: <span class="string">&#x27;bar&#x27;</span> &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">const</span> error = createError(<span class="string">&#x27;Boom!&#x27;</span>, config, <span class="string">&#x27;SOMETHING&#x27;</span>, request, response)</span><br><span class="line">    expect(error <span class="keyword">instanceof</span> <span class="built_in">Error</span>).toBeTruthy()</span><br><span class="line">    expect(error.message).toBe(<span class="string">&#x27;Boom!&#x27;</span>)</span><br><span class="line">    expect(error.config).toBe(config)</span><br><span class="line">    expect(error.code).toBe(<span class="string">&#x27;SOMETHING&#x27;</span>)</span><br><span class="line">    expect(error.request).toBe(request)</span><br><span class="line">    expect(error.response).toBe(response)</span><br><span class="line">    expect(error.isAxiosError).toBeTruthy()</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>该模块跑完我们会发现，分支覆盖率是在 <code>50%</code>，因为第十七行代码</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">super</span>(message)</span><br></pre></td></tr></table></figure>

<p>这个是 <code>super</code> 继承对测试覆盖率支持的坑，目前没有好的解决方案，可以先忽略。</p>
<h2 id="29-6-headers-模块测试"><a href="#29-6-headers-模块测试" class="headerlink" title="29.6 headers 模块测试"></a>29.6 headers 模块测试</h2><p><code>test/helpers/headers.spec.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; parseHeaders, processHeaders, flattenHeaders &#125; <span class="keyword">from</span> <span class="string">&#x27;../../src/helpers/headers&#x27;</span></span><br><span class="line"></span><br><span class="line">describe(<span class="string">&#x27;helpers:header&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  describe(<span class="string">&#x27;parseHeaders&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    test(<span class="string">&#x27;should parse headers&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> parsed = parseHeaders(</span><br><span class="line">        <span class="string">&#x27;Content-Type: application/json\r\n&#x27;</span> +</span><br><span class="line">          <span class="string">&#x27;Connection: keep-alive\r\n&#x27;</span> +</span><br><span class="line">          <span class="string">&#x27;Transfer-Encoding: chunked\r\n&#x27;</span> +</span><br><span class="line">          <span class="string">&#x27;Date: Tue, 21 May 2019 09:23:44 GMT\r\n&#x27;</span> +</span><br><span class="line">          <span class="string">&#x27;:aa\r\n&#x27;</span> +</span><br><span class="line">          <span class="string">&#x27;key:&#x27;</span></span><br><span class="line">      )</span><br><span class="line"></span><br><span class="line">      expect(parsed[<span class="string">&#x27;content-type&#x27;</span>]).toBe(<span class="string">&#x27;application/json&#x27;</span>)</span><br><span class="line">      expect(parsed[<span class="string">&#x27;connection&#x27;</span>]).toBe(<span class="string">&#x27;keep-alive&#x27;</span>)</span><br><span class="line">      expect(parsed[<span class="string">&#x27;transfer-encoding&#x27;</span>]).toBe(<span class="string">&#x27;chunked&#x27;</span>)</span><br><span class="line">      expect(parsed[<span class="string">&#x27;date&#x27;</span>]).toBe(<span class="string">&#x27;Tue, 21 May 2019 09:23:44 GMT&#x27;</span>)</span><br><span class="line">      expect(parsed[<span class="string">&#x27;key&#x27;</span>]).toBe(<span class="string">&#x27;&#x27;</span>)</span><br><span class="line">      <span class="keyword">const</span> emptyKeyVal = <span class="built_in">Object</span>.values(parsed).includes(<span class="string">&#x27;empty key&#x27;</span>)</span><br><span class="line">      expect(emptyKeyVal).toBeFalsy()</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should return empty object if headers is empty string&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(parseHeaders(<span class="string">&#x27;&#x27;</span>)).toEqual(&#123;&#125;)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  describe(<span class="string">&#x27;processHeaders&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    test(<span class="string">&#x27;should normalize Content-Type header name&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> headers: <span class="built_in">any</span> = &#123;</span><br><span class="line">        <span class="string">&#x27;conTenT-Type&#x27;</span>: <span class="string">&#x27;foo/bar&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;Content-length&#x27;</span>: <span class="number">1024</span></span><br><span class="line">      &#125;</span><br><span class="line">      processHeaders(headers, &#123;&#125;)</span><br><span class="line">      expect(headers[<span class="string">&#x27;Content-Type&#x27;</span>]).toBe(<span class="string">&#x27;foo/bar&#x27;</span>)</span><br><span class="line">      expect(headers[<span class="string">&#x27;conTenT-Type&#x27;</span>]).toBeUndefined()</span><br><span class="line">      expect(headers[<span class="string">&#x27;Content-length&#x27;</span>]).toBe(<span class="number">1024</span>)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should set Content-Type if not set and data is PlainObject&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> headers: <span class="built_in">any</span> = &#123;&#125;</span><br><span class="line">      processHeaders(headers, &#123; <span class="attr">a</span>: <span class="number">1</span> &#125;)</span><br><span class="line">      expect(headers[<span class="string">&#x27;Content-Type&#x27;</span>]).toBe(<span class="string">&#x27;application/json;charset=utf-8&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should set not Content-Type if not set and data is not PlainObject&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> headers: <span class="built_in">any</span> = &#123;&#125;</span><br><span class="line">      processHeaders(headers, <span class="keyword">new</span> URLSearchParams(<span class="string">&#x27;a=b&#x27;</span>))</span><br><span class="line">      expect(headers[<span class="string">&#x27;Content-Type&#x27;</span>]).toBeUndefined()</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should do nothing if headers is undefined or null&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(processHeaders(<span class="literal">undefined</span>, &#123;&#125;)).toBeUndefined()</span><br><span class="line">      expect(processHeaders(<span class="literal">null</span>, &#123;&#125;)).toBeNull()</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  describe(<span class="string">&#x27;flattenHeaders&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    test(<span class="string">&#x27;should flatten the headers and include common headers&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> headers = &#123;</span><br><span class="line">        Accept: <span class="string">&#x27;application/json&#x27;</span>,</span><br><span class="line">        common: &#123;</span><br><span class="line">          <span class="string">&#x27;X-COMMON-HEADER&#x27;</span>: <span class="string">&#x27;commonHeaderValue&#x27;</span></span><br><span class="line">        &#125;,</span><br><span class="line">        get: &#123;</span><br><span class="line">          <span class="string">&#x27;X-GET-HEADER&#x27;</span>: <span class="string">&#x27;getHeaderValue&#x27;</span></span><br><span class="line">        &#125;,</span><br><span class="line">        post: &#123;</span><br><span class="line">          <span class="string">&#x27;X-POST-HEADER&#x27;</span>: <span class="string">&#x27;postHeaderValue&#x27;</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      expect(flattenHeaders(headers, <span class="string">&#x27;get&#x27;</span>)).toEqual(&#123;</span><br><span class="line">        Accept: <span class="string">&#x27;application/json&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;X-COMMON-HEADER&#x27;</span>: <span class="string">&#x27;commonHeaderValue&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;X-GET-HEADER&#x27;</span>: <span class="string">&#x27;getHeaderValue&#x27;</span></span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should flatten the headers without common headers&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> headers = &#123;</span><br><span class="line">        Accept: <span class="string">&#x27;application/json&#x27;</span>,</span><br><span class="line">        get: &#123;</span><br><span class="line">          <span class="string">&#x27;X-GET-HEADER&#x27;</span>: <span class="string">&#x27;getHeaderValue&#x27;</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      expect(flattenHeaders(headers, <span class="string">&#x27;patch&#x27;</span>)).toEqual(&#123;</span><br><span class="line">        Accept: <span class="string">&#x27;application/json&#x27;</span></span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should do nothing if headers is undefined or null&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(flattenHeaders(<span class="literal">undefined</span>, <span class="string">&#x27;get&#x27;</span>)).toBeUndefined()</span><br><span class="line">      expect(flattenHeaders(<span class="literal">null</span>, <span class="string">&#x27;post&#x27;</span>)).toBeNull()</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>运行后，我们会发现 <code>parseHeaders</code> 测试组的 <code>should parse headers</code> 测试没通过，<code>expect(parsed[&#39;date&#39;]).toBe(&#39;Thu, 27 May 2021 20:31:44 GMT&#39;)</code> 我们期望解析后的 <code>date</code> 字段是 <code>Thu, 27 May 2021 20:31:44 GMT</code>，而实际的值是 <code>Thu, 27 May 2021 20</code>。</p>
<p>测试没通过，我们检查一下代码，发现我们 <code>parseHeaders</code> 的代码逻辑漏洞，我们只考虑了第一个 “:” 号，没考虑后半部分的字符串内部也可能有 “:”，按我们现有的逻辑就会把字符串中 “:” 后面部分都截断了。</p>
<p>因此我们修改 <code>parseHeaders</code> 的实现逻辑。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">parseHeaders</span>(<span class="params">headers: <span class="built_in">string</span></span>): <span class="title">any</span> </span>&#123;</span><br><span class="line">  <span class="keyword">let</span> parsed = <span class="built_in">Object</span>.create(<span class="literal">null</span>)</span><br><span class="line">  <span class="keyword">if</span> (!headers) &#123;</span><br><span class="line">    <span class="keyword">return</span> parsed</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  headers.split(<span class="string">&#x27;\r\n&#x27;</span>).forEach(<span class="function"><span class="params">line</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> [key, ...vals] = line.split(<span class="string">&#x27;:&#x27;</span>)</span><br><span class="line">    key = key.trim().toLowerCase()</span><br><span class="line">    <span class="keyword">if</span> (!key) &#123;</span><br><span class="line">      <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">let</span> val = vals.join(<span class="string">&#x27;:&#x27;</span>).trim()</span><br><span class="line">    parsed[key] = val</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> parsed</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这样我们再重新跑测试，就会通过了。</p>
<h2 id="29-7-url-模块测试"><a href="#29-7-url-模块测试" class="headerlink" title="29.7 url 模块测试"></a>29.7 url 模块测试</h2><p><code>test/helpers/url.spec.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; buildURL, isAbsoluteURL, combineURL, isURLSameOrigin &#125; <span class="keyword">from</span> <span class="string">&#x27;../../src/helpers/url&#x27;</span></span><br><span class="line"></span><br><span class="line">describe(<span class="string">&#x27;helpers:url&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  describe(<span class="string">&#x27;buildURL&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    test(<span class="string">&#x27;should support null params&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(buildURL(<span class="string">&#x27;/foo&#x27;</span>)).toBe(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should support params&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(</span><br><span class="line">        buildURL(<span class="string">&#x27;/foo&#x27;</span>, &#123;</span><br><span class="line">          foo: <span class="string">&#x27;bar&#x27;</span></span><br><span class="line">        &#125;)</span><br><span class="line">      ).toBe(<span class="string">&#x27;/foo?foo=bar&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should ignore if some param value is null&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(</span><br><span class="line">        buildURL(<span class="string">&#x27;/foo&#x27;</span>, &#123;</span><br><span class="line">          foo: <span class="string">&#x27;bar&#x27;</span>,</span><br><span class="line">          baz: <span class="literal">null</span></span><br><span class="line">        &#125;)</span><br><span class="line">      ).toBe(<span class="string">&#x27;/foo?foo=bar&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should ignore if the only param value is null&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(</span><br><span class="line">        buildURL(<span class="string">&#x27;/foo&#x27;</span>, &#123;</span><br><span class="line">          baz: <span class="literal">null</span></span><br><span class="line">        &#125;)</span><br><span class="line">      ).toBe(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should support object params&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(</span><br><span class="line">        buildURL(<span class="string">&#x27;/foo&#x27;</span>, &#123;</span><br><span class="line">          foo: &#123;</span><br><span class="line">            bar: <span class="string">&#x27;baz&#x27;</span></span><br><span class="line">          &#125;</span><br><span class="line">        &#125;)</span><br><span class="line">      ).toBe(<span class="string">&#x27;/foo?foo=&#x27;</span> + <span class="built_in">encodeURI</span>(<span class="string">&#x27;&#123;&quot;bar&quot;:&quot;baz&quot;&#125;&#x27;</span>))</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should support date params&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> date = <span class="keyword">new</span> <span class="built_in">Date</span>()</span><br><span class="line"></span><br><span class="line">      expect(</span><br><span class="line">        buildURL(<span class="string">&#x27;/foo&#x27;</span>, &#123;</span><br><span class="line">          date: date</span><br><span class="line">        &#125;)</span><br><span class="line">      ).toBe(<span class="string">&#x27;/foo?date=&#x27;</span> + date.toISOString())</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should support array params&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(</span><br><span class="line">        buildURL(<span class="string">&#x27;/foo&#x27;</span>, &#123;</span><br><span class="line">          foo: [<span class="string">&#x27;bar&#x27;</span>, <span class="string">&#x27;baz&#x27;</span>]</span><br><span class="line">        &#125;)</span><br><span class="line">      ).toBe(<span class="string">&#x27;/foo?foo[]=bar&amp;foo[]=baz&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should support special char params&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(</span><br><span class="line">        buildURL(<span class="string">&#x27;/foo&#x27;</span>, &#123;</span><br><span class="line">          foo: <span class="string">&#x27;@:$, &#x27;</span></span><br><span class="line">        &#125;)</span><br><span class="line">      ).toBe(<span class="string">&#x27;/foo?foo=@:$,+&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should support existing params&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(</span><br><span class="line">        buildURL(<span class="string">&#x27;/foo?foo=bar&#x27;</span>, &#123;</span><br><span class="line">          bar: <span class="string">&#x27;baz&#x27;</span></span><br><span class="line">        &#125;)</span><br><span class="line">      ).toBe(<span class="string">&#x27;/foo?foo=bar&amp;bar=baz&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should correct discard url hash mark&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(</span><br><span class="line">        buildURL(<span class="string">&#x27;/foo?foo=bar#hash&#x27;</span>, &#123;</span><br><span class="line">          query: <span class="string">&#x27;baz&#x27;</span></span><br><span class="line">        &#125;)</span><br><span class="line">      ).toBe(<span class="string">&#x27;/foo?foo=bar&amp;query=baz&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should use serializer if provided&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> serializer = jest.fn(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&#x27;foo=bar&#x27;</span></span><br><span class="line">      &#125;)</span><br><span class="line">      <span class="keyword">const</span> params = &#123; <span class="attr">foo</span>: <span class="string">&#x27;bar&#x27;</span> &#125;</span><br><span class="line">      expect(buildURL(<span class="string">&#x27;/foo&#x27;</span>, params, serializer)).toBe(<span class="string">&#x27;/foo?foo=bar&#x27;</span>)</span><br><span class="line">      expect(serializer).toHaveBeenCalled()</span><br><span class="line">      expect(serializer).toHaveBeenCalledWith(params)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should support URLSearchParams&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(buildURL(<span class="string">&#x27;/foo&#x27;</span>, <span class="keyword">new</span> URLSearchParams(<span class="string">&#x27;bar=baz&#x27;</span>))).toBe(<span class="string">&#x27;/foo?bar=baz&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  describe(<span class="string">&#x27;isAbsoluteURL&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    test(<span class="string">&#x27;should return true if URL begins with valid scheme name&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(isAbsoluteURL(<span class="string">&#x27;https://api.github.com/users&#x27;</span>)).toBeTruthy()</span><br><span class="line">      expect(isAbsoluteURL(<span class="string">&#x27;custom-scheme-v1.0://example.com/&#x27;</span>)).toBeTruthy()</span><br><span class="line">      expect(isAbsoluteURL(<span class="string">&#x27;HTTP://example.com/&#x27;</span>)).toBeTruthy()</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should return false if URL begins with invalid scheme name&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(isAbsoluteURL(<span class="string">&#x27;123://example.com/&#x27;</span>)).toBeFalsy()</span><br><span class="line">      expect(isAbsoluteURL(<span class="string">&#x27;!valid://example.com/&#x27;</span>)).toBeFalsy()</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should return true if URL is protocol-relative&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(isAbsoluteURL(<span class="string">&#x27;//example.com/&#x27;</span>)).toBeTruthy()</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should return false if URL is relative&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(isAbsoluteURL(<span class="string">&#x27;/foo&#x27;</span>)).toBeFalsy()</span><br><span class="line">      expect(isAbsoluteURL(<span class="string">&#x27;foo&#x27;</span>)).toBeFalsy()</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  describe(<span class="string">&#x27;combineURL&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    test(<span class="string">&#x27;should combine URL&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(combineURL(<span class="string">&#x27;https://api.github.com&#x27;</span>, <span class="string">&#x27;/users&#x27;</span>)).toBe(<span class="string">&#x27;https://api.github.com/users&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should remove duplicate slashes&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(combineURL(<span class="string">&#x27;https://api.github.com/&#x27;</span>, <span class="string">&#x27;/users&#x27;</span>)).toBe(<span class="string">&#x27;https://api.github.com/users&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should insert missing slash&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(combineURL(<span class="string">&#x27;https://api.github.com&#x27;</span>, <span class="string">&#x27;users&#x27;</span>)).toBe(<span class="string">&#x27;https://api.github.com/users&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should not insert slash when relative url missing/empty&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(combineURL(<span class="string">&#x27;https://api.github.com/users&#x27;</span>, <span class="string">&#x27;&#x27;</span>)).toBe(<span class="string">&#x27;https://api.github.com/users&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should allow a single slash for relative url&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(combineURL(<span class="string">&#x27;https://api.github.com/users&#x27;</span>, <span class="string">&#x27;/&#x27;</span>)).toBe(<span class="string">&#x27;https://api.github.com/users/&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  describe(<span class="string">&#x27;isURLSameOrigin&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    test(<span class="string">&#x27;should detect same origin&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(isURLSameOrigin(<span class="built_in">window</span>.location.href)).toBeTruthy()</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should detect different origin&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(isURLSameOrigin(<span class="string">&#x27;https://github.com/axios/axios&#x27;</span>)).toBeFalsy()</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>这里要注意的是，我们使用了 <a target="_blank" rel="noopener" href="https://jestjs.io/docs/en/jest-object#jestfnimplementation"><code>jest.fn</code></a> 去模拟了一个函数，这个也是在编写 Jest 测试中非常常用的一个 API。</p>
<p>至此，我们就实现了 <code>ts-axios</code> 库 <code>helpers</code> 目录下所有模块的测试，并把该目录下的测试覆盖率达到了近乎 100% 的覆盖率。下面的章节我们就开始测试 <code>ts-axios</code> 的核心流程，针对不同的 <code>feature</code> 去编写单元测试了。</p>
<h1 id="30-请求模块单元测试"><a href="#30-请求模块单元测试" class="headerlink" title="30.请求模块单元测试"></a>30.请求模块单元测试</h1><p>请求模块是 axios 最基础的模块，通过一个 axios 方法发送 Ajax 请求。</p>
<h2 id="30-1-jasmine-ajax"><a href="#30-1-jasmine-ajax" class="headerlink" title="30.1 jasmine-ajax"></a>30.1 jasmine-ajax</h2><p><a target="_blank" rel="noopener" href="https://jasmine.github.io/pages/getting_started.html">Jasmine</a> 是一个 BDD(行为驱动开发)的测试框架，它有很多成熟的插件，比如我们要用到的 <a target="_blank" rel="noopener" href="https://github.com/jasmine/jasmine-ajax"><code>jasmine-ajax</code></a>，它会为我们发出的 Ajax 请求根据规范定义一组假的响应，并跟踪我们发出的Ajax请求，可以让我们方便的为结果做断言。</p>
<p>其实 Jest 也可以去写插件，但并没有现成的 Ajax 相关的 Jest 插件，但是 Jest 测试中我们仍然可以使用 Jasmine 相关的插件，只需要做一些小小的配置即可。</p>
<p>当然，未来我也会考虑去编写一个 Ajax 相关的 Jest 插件，目前我们仍然使用 <code>jasmine-ajax</code> 去配合我们编写测试。</p>
<p><code>jasmine-ajax</code> 依赖 <code>jasmine-core</code>，因此首先我们要安装几个依赖包，<code>jasmine-ajax</code>、<code>jasmine-core</code> 和 <code>@types/jasmine-ajax</code>。</p>
<p>这个时候我们需要去修改 <code>test/boot.ts</code> 文件，因为每次跑具体测试代码之前会先运行该文件，我们可以在这里去初始化 <code>jasmine-ajax</code>。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> JasmineCore = <span class="built_in">require</span>(<span class="string">&#x27;jasmine-core&#x27;</span>)</span><br><span class="line"><span class="comment">// @ts-ignore</span></span><br><span class="line"><span class="built_in">global</span>.getJasmineRequireObj = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> JasmineCore</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">require</span>(<span class="string">&#x27;jasmine-ajax&#x27;</span>)</span><br></pre></td></tr></table></figure>

<p>这里为了让 <code>jasmine-ajax</code> 插件运行成功，我们需要手动添加全局的 <code>getJasmineRequireObj</code> 方法，参考 <a target="_blank" rel="noopener" href="https://github.com/jasmine/jasmine-ajax/issues/178">issue</a>。</p>
<p>接下来，我们就开始编写请求模块的单元测试。</p>
<h2 id="30-2-测试代码编写"><a href="#30-2-测试代码编写" class="headerlink" title="30.2 测试代码编写"></a>30.2 测试代码编写</h2><p><code>test/requests.spec.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios, &#123; AxiosResponse, AxiosError &#125; <span class="keyword">from</span> <span class="string">&#x27;../src/index&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; getAjaxRequest &#125; <span class="keyword">from</span> <span class="string">&#x27;./helper&#x27;</span></span><br><span class="line"></span><br><span class="line">describe(<span class="string">&#x27;requests&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  beforeEach(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    jasmine.Ajax.install()</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  afterEach(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    jasmine.Ajax.uninstall()</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should treat single string arg as url&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    axios(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.url).toBe(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line">      expect(request.method).toBe(<span class="string">&#x27;GET&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should treat method value as lowercase string&#x27;</span>, <span class="function"><span class="params">done</span> =&gt;</span> &#123;</span><br><span class="line">    axios(&#123;</span><br><span class="line">      url: <span class="string">&#x27;/foo&#x27;</span>,</span><br><span class="line">      method: <span class="string">&#x27;POST&#x27;</span></span><br><span class="line">    &#125;).then(<span class="function"><span class="params">response</span> =&gt;</span> &#123;</span><br><span class="line">      expect(response.config.method).toBe(<span class="string">&#x27;post&#x27;</span>)</span><br><span class="line">      done()</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      request.respondWith(&#123;</span><br><span class="line">        status: <span class="number">200</span></span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should reject on network errors&#x27;</span>, <span class="function"><span class="params">done</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> resolveSpy = jest.fn(<span class="function">(<span class="params">res: AxiosResponse</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> res</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> rejectSpy = jest.fn(<span class="function">(<span class="params">e: AxiosError</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> e</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    jasmine.Ajax.uninstall()</span><br><span class="line"></span><br><span class="line">    axios(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line">      .then(resolveSpy)</span><br><span class="line">      .catch(rejectSpy)</span><br><span class="line">      .then(next)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">next</span>(<span class="params">reason: AxiosResponse | AxiosError</span>) </span>&#123;</span><br><span class="line">      expect(resolveSpy).not.toHaveBeenCalled()</span><br><span class="line">      expect(rejectSpy).toHaveBeenCalled()</span><br><span class="line">      expect(reason <span class="keyword">instanceof</span> <span class="built_in">Error</span>).toBeTruthy()</span><br><span class="line">      expect((reason <span class="keyword">as</span> AxiosError).message).toBe(<span class="string">&#x27;Network Error&#x27;</span>)</span><br><span class="line">      expect(reason.request).toEqual(expect.any(XMLHttpRequest))</span><br><span class="line"></span><br><span class="line">      jasmine.Ajax.install()</span><br><span class="line"></span><br><span class="line">      done()</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should reject when request timeout&#x27;</span>, <span class="function"><span class="params">done</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> err: AxiosError</span><br><span class="line"></span><br><span class="line">    axios(<span class="string">&#x27;/foo&#x27;</span>, &#123;</span><br><span class="line">      timeout: <span class="number">2000</span>,</span><br><span class="line">      method: <span class="string">&#x27;post&#x27;</span></span><br><span class="line">    &#125;).catch(<span class="function"><span class="params">error</span> =&gt;</span> &#123;</span><br><span class="line">      err = error</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="comment">// @ts-ignore</span></span><br><span class="line">      request.eventBus.trigger(<span class="string">&#x27;timeout&#x27;</span>)</span><br><span class="line"></span><br><span class="line">      <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        expect(err <span class="keyword">instanceof</span> <span class="built_in">Error</span>).toBeTruthy()</span><br><span class="line">        expect(err.message).toBe(<span class="string">&#x27;Timeout of 2000 ms exceeded&#x27;</span>)</span><br><span class="line">        done()</span><br><span class="line">      &#125;, <span class="number">100</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should reject when validateStatus returns false&#x27;</span>, <span class="function"><span class="params">done</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> resolveSpy = jest.fn(<span class="function">(<span class="params">res: AxiosResponse</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> res</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> rejectSpy = jest.fn(<span class="function">(<span class="params">e: AxiosError</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> e</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    axios(<span class="string">&#x27;/foo&#x27;</span>, &#123;</span><br><span class="line">      <span class="function"><span class="title">validateStatus</span>(<span class="params">status</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> status !== <span class="number">500</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">      .then(resolveSpy)</span><br><span class="line">      .catch(rejectSpy)</span><br><span class="line">      .then(next)</span><br><span class="line"></span><br><span class="line">    getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      request.respondWith(&#123;</span><br><span class="line">        status: <span class="number">500</span></span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">next</span>(<span class="params">reason: AxiosError | AxiosResponse</span>) </span>&#123;</span><br><span class="line">      expect(resolveSpy).not.toHaveBeenCalled()</span><br><span class="line">      expect(rejectSpy).toHaveBeenCalled()</span><br><span class="line">      expect(reason <span class="keyword">instanceof</span> <span class="built_in">Error</span>).toBeTruthy()</span><br><span class="line">      expect((reason <span class="keyword">as</span> AxiosError).message).toBe(<span class="string">&#x27;Request failed with status code 500&#x27;</span>)</span><br><span class="line">      expect((reason <span class="keyword">as</span> AxiosError).response!.status).toBe(<span class="number">500</span>)</span><br><span class="line"></span><br><span class="line">      done()</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should resolve when validateStatus returns true&#x27;</span>, <span class="function"><span class="params">done</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> resolveSpy = jest.fn(<span class="function">(<span class="params">res: AxiosResponse</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> res</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> rejectSpy = jest.fn(<span class="function">(<span class="params">e: AxiosError</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> e</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    axios(<span class="string">&#x27;/foo&#x27;</span>, &#123;</span><br><span class="line">      <span class="function"><span class="title">validateStatus</span>(<span class="params">status</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> status === <span class="number">500</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">      .then(resolveSpy)</span><br><span class="line">      .catch(rejectSpy)</span><br><span class="line">      .then(next)</span><br><span class="line"></span><br><span class="line">    getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      request.respondWith(&#123;</span><br><span class="line">        status: <span class="number">500</span></span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">next</span>(<span class="params">res: AxiosResponse | AxiosError</span>) </span>&#123;</span><br><span class="line">      expect(resolveSpy).toHaveBeenCalled()</span><br><span class="line">      expect(rejectSpy).not.toHaveBeenCalled()</span><br><span class="line">      expect(res.config.url).toBe(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">      done()</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should return JSON when resolved&#x27;</span>, <span class="function"><span class="params">done</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> response: AxiosResponse</span><br><span class="line"></span><br><span class="line">    axios(<span class="string">&#x27;/api/account/signup&#x27;</span>, &#123;</span><br><span class="line">      auth: &#123;</span><br><span class="line">        username: <span class="string">&#x27;&#x27;</span>,</span><br><span class="line">        password: <span class="string">&#x27;&#x27;</span></span><br><span class="line">      &#125;,</span><br><span class="line">      method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">      headers: &#123;</span><br><span class="line">        Accept: <span class="string">&#x27;application/json&#x27;</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;).then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">      response = res</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      request.respondWith(&#123;</span><br><span class="line">        status: <span class="number">200</span>,</span><br><span class="line">        statusText: <span class="string">&#x27;OK&#x27;</span>,</span><br><span class="line">        responseText: <span class="string">&#x27;&#123;&quot;a&quot;: 1&#125;&#x27;</span></span><br><span class="line">      &#125;)</span><br><span class="line"></span><br><span class="line">      <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        expect(response.data).toEqual(&#123; <span class="attr">a</span>: <span class="number">1</span> &#125;)</span><br><span class="line">        done()</span><br><span class="line">      &#125;, <span class="number">100</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should return JSON when rejecting&#x27;</span>, <span class="function"><span class="params">done</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> response: AxiosResponse</span><br><span class="line"></span><br><span class="line">    axios(<span class="string">&#x27;/api/account/signup&#x27;</span>, &#123;</span><br><span class="line">      auth: &#123;</span><br><span class="line">        username: <span class="string">&#x27;&#x27;</span>,</span><br><span class="line">        password: <span class="string">&#x27;&#x27;</span></span><br><span class="line">      &#125;,</span><br><span class="line">      method: <span class="string">&#x27;post&#x27;</span>,</span><br><span class="line">      headers: &#123;</span><br><span class="line">        Accept: <span class="string">&#x27;application/json&#x27;</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;).catch(<span class="function"><span class="params">error</span> =&gt;</span> &#123;</span><br><span class="line">      response = error.response</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      request.respondWith(&#123;</span><br><span class="line">        status: <span class="number">400</span>,</span><br><span class="line">        statusText: <span class="string">&#x27;Bad Request&#x27;</span>,</span><br><span class="line">        responseText: <span class="string">&#x27;&#123;&quot;error&quot;: &quot;BAD USERNAME&quot;, &quot;code&quot;: 1&#125;&#x27;</span></span><br><span class="line">      &#125;)</span><br><span class="line"></span><br><span class="line">      <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        expect(<span class="keyword">typeof</span> response.data).toBe(<span class="string">&#x27;object&#x27;</span>)</span><br><span class="line">        expect(response.data.error).toBe(<span class="string">&#x27;BAD USERNAME&#x27;</span>)</span><br><span class="line">        expect(response.data.code).toBe(<span class="number">1</span>)</span><br><span class="line">        done()</span><br><span class="line">      &#125;, <span class="number">100</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should supply correct response&#x27;</span>, <span class="function"><span class="params">done</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> response: AxiosResponse</span><br><span class="line"></span><br><span class="line">    axios.post(<span class="string">&#x27;/foo&#x27;</span>).then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">      response = res</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      request.respondWith(&#123;</span><br><span class="line">        status: <span class="number">200</span>,</span><br><span class="line">        statusText: <span class="string">&#x27;OK&#x27;</span>,</span><br><span class="line">        responseText: <span class="string">&#x27;&#123;&quot;foo&quot;: &quot;bar&quot;&#125;&#x27;</span>,</span><br><span class="line">        responseHeaders: &#123;</span><br><span class="line">          <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;)</span><br><span class="line"></span><br><span class="line">      <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        expect(response.data.foo).toBe(<span class="string">&#x27;bar&#x27;</span>)</span><br><span class="line">        expect(response.status).toBe(<span class="number">200</span>)</span><br><span class="line">        expect(response.statusText).toBe(<span class="string">&#x27;OK&#x27;</span>)</span><br><span class="line">        expect(response.headers[<span class="string">&#x27;content-type&#x27;</span>]).toBe(<span class="string">&#x27;application/json&#x27;</span>)</span><br><span class="line">        done()</span><br><span class="line">      &#125;, <span class="number">100</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should allow overriding Content-Type header case-insensitive&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> response: AxiosResponse</span><br><span class="line"></span><br><span class="line">    axios</span><br><span class="line">      .post(</span><br><span class="line">        <span class="string">&#x27;/foo&#x27;</span>,</span><br><span class="line">        &#123; <span class="attr">prop</span>: <span class="string">&#x27;value&#x27;</span> &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">          headers: &#123;</span><br><span class="line">            <span class="string">&#x27;content-type&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span></span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      )</span><br><span class="line">      .then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">        response = res</span><br><span class="line">      &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.requestHeaders[<span class="string">&#x27;Content-Type&#x27;</span>]).toBe(<span class="string">&#x27;application/json&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>我们要注意的一些点，在这里列出：</p>
<ul>
<li>beforeEach &amp; afterEach</li>
</ul>
<p><a target="_blank" rel="noopener" href="https://jestjs.io/docs/en/api#beforeeachfn-timeout">beforeEach</a>表示每个测试用例运行前的钩子函数，在这里我们执行 <code>jasmine.Ajax.install()</code> 安装 <code>jasmine.Ajax</code>。</p>
<p><a target="_blank" rel="noopener" href="https://jestjs.io/docs/en/api#aftereachfn-timeout">afterEach</a>表示每个测试用例运行后的钩子函数，在这里我们执行 <code>jasmine.Ajax.uninstall()</code> 卸载 <code>jasmine.Ajax</code>。</p>
<ul>
<li><code>getAjaxRequest</code></li>
</ul>
<p><code>getAjaxRequest</code> 是我们在 <code>test/helper.ts</code> 定义的一个辅助方法，通过 <code>jasmine.Ajax.requests.mostRecent()</code> 拿到最近一次请求的 <code>request</code> 对象，这个 <code>request</code> 对象是 <code>jasmine-ajax</code> 库伪造的 <code>xhr</code> 对象，它模拟了 <code>xhr</code> 对象上的方法，并且提供一些 <code>api</code> 让我们使用，比如 <code>request.respondWith</code> 方法返回一个响应。</p>
<p><code>test/helper.ts</code> </p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">getAjaxRequest</span>(<span class="params"></span>): <span class="title">Promise</span>&lt;<span class="title">JasmineAjaxRequest</span>&gt; </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="keyword">function</span>(<span class="params">resolve</span>) </span>&#123;</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> resolve(jasmine.Ajax.requests.mostRecent())</span><br><span class="line">    &#125;, <span class="number">0</span>)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>异步测试</li>
</ul>
<p>注意到我们这里大部分的测试用例不再是同步的代码了，几乎都是一些异步逻辑，Jest 非常好地支持<a target="_blank" rel="noopener" href="https://jestjs.io/docs/en/asynchronous">异步测试代码</a>。通常有 2 种解决方案。</p>
<p>第一种是利用 <code>done</code> 参数，每个测试用例函数有一个 <code>done</code> 参数，一旦我们使用了该参数，只有当 <code>done</code> 函数执行的时候表示这个测试用例结束。</p>
<p>第二种是我们的测试函数返回一个 Promise 对象，一旦这个 Promise 对象 <code>resolve</code> 了，表示这个测试结束。</p>
<ul>
<li>expect.any(constructor)</li>
</ul>
<p>它表示匹配任意由 <code>constructor</code> 创建的对象实例。</p>
<ul>
<li><code>request.eventBus.trigger</code></li>
</ul>
<p>由于 <code>request.responseTimeout</code> 方法内部依赖了 <code>jasmine.clock</code> 方法会导致运行失败，这里我直接用了 <code>request.eventBus.trigger(&#39;timeout&#39;)</code> 方法触发了 <code>timeout</code> 事件。因为这个方法不在接口定义中，所以需要加 <code>// @ts-ignore</code>。</p>
<p>另外，我们在测试中发现 2 个 case 没有通过。</p>
<p>另外，我们在测试中发现 2 个 case 没有通过。</p>
<p>第一个是 <code>should treat method value as lowercase string</code>，这个测试用例是我们发送请求的 <code> method</code> 需要转换成小写字符串，这么做的目的也是为了之后 <code>flattenHeaders</code> 能正常处理这些 <code>method</code>，所以我们需要修改源码逻辑。</p>
<p><code>core/Axios.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">request(url: <span class="built_in">any</span>, config?: <span class="built_in">any</span>): AxiosPromise &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">typeof</span> url === <span class="string">&#x27;string&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!config) &#123;</span><br><span class="line">      config = &#123;&#125;</span><br><span class="line">    &#125;</span><br><span class="line">    config.url = url</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    config = url</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  config = mergeConfig(<span class="built_in">this</span>.defaults, config)</span><br><span class="line">  config.method = config.method.toLowerCase()</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在合并配置后，我们需要把 <code>config.method</code> 转成小写字符串。</p>
<p>另一个是 <code>should return JSON when rejecting</code>，这个测试用例是当我们发送请求失败后，也能把响应数据转换成 JSON 格式，所以也需要修改源码逻辑。</p>
<p><code>core/dispatchRequest.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="function"><span class="keyword">function</span> <span class="title">dispatchRequest</span>(<span class="params">config: AxiosRequestConfig</span>): <span class="title">AxiosPromise</span> </span>&#123;</span><br><span class="line">  throwIfCancellationRequested(config)</span><br><span class="line">  processConfig(config)</span><br><span class="line">  <span class="keyword">return</span> xhr(config).then(</span><br><span class="line">    res =&gt; &#123;</span><br><span class="line">      <span class="keyword">return</span> transformResponseData(res)</span><br><span class="line">    &#125;,</span><br><span class="line">    e =&gt; &#123;</span><br><span class="line">      <span class="keyword">if</span> (e &amp;&amp; e.response) &#123;</span><br><span class="line">        e.response = transformResponseData(e.response)</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> <span class="built_in">Promise</span>.reject(e)</span><br><span class="line">    &#125;</span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>除了对正常情况的响应数据做转换，我们也需要对异常情况的响应数据做转换。</p>
<p>至此我们完成了 <code>ts-axios</code> 库对请求模块的测试，下一节课我们会从业务的角度来测试 <code>headers</code> 模块。</p>
<h1 id="31-headers-模块单元测试"><a href="#31-headers-模块单元测试" class="headerlink" title="31.headers 模块单元测试"></a>31.headers 模块单元测试</h1><p>之前我们测试了 <code>headers</code> 的基础方法模块，接下来我们会从业务角度测试 <code>headers</code> 的相关业务逻辑。</p>
<h2 id="31-1-测试代码编写"><a href="#31-1-测试代码编写" class="headerlink" title="31.1 测试代码编写"></a>31.1 测试代码编写</h2><p><code>test/headers.spec.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;../src/index&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; getAjaxRequest &#125; <span class="keyword">from</span> <span class="string">&#x27;./helper&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">testHeaderValue</span>(<span class="params">headers: <span class="built_in">any</span>, key: <span class="built_in">string</span>, val?: <span class="built_in">string</span></span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">  <span class="keyword">let</span> found = <span class="literal">false</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> k <span class="keyword">in</span> headers) &#123;</span><br><span class="line">    <span class="keyword">if</span> (k.toLowerCase() === key.toLowerCase()) &#123;</span><br><span class="line">      found = <span class="literal">true</span></span><br><span class="line">      expect(headers[k]).toBe(val)</span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (!found) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> val === <span class="string">&#x27;undefined&#x27;</span>) &#123;</span><br><span class="line">      expect(headers.hasOwnProperty(key)).toBeFalsy()</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(key + <span class="string">&#x27; was not found in headers&#x27;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">describe(<span class="string">&#x27;headers&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  beforeEach(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    jasmine.Ajax.install()</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  afterEach(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    jasmine.Ajax.uninstall()</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should use default common headers&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> headers = axios.defaults.headers.common</span><br><span class="line"></span><br><span class="line">    axios(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">for</span> (<span class="keyword">let</span> key <span class="keyword">in</span> headers) &#123;</span><br><span class="line">        <span class="keyword">if</span> (headers.hasOwnProperty(key)) &#123;</span><br><span class="line">          expect(request.requestHeaders[key]).toEqual(headers[key])</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should add extra headers for post&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    axios.post(<span class="string">&#x27;/foo&#x27;</span>, <span class="string">&#x27;fizz=buzz&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      testHeaderValue(request.requestHeaders, <span class="string">&#x27;Content-Type&#x27;</span>, <span class="string">&#x27;application/x-www-form-urlencoded&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should use application/json when posting an object&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    axios.post(<span class="string">&#x27;/foo/bar&#x27;</span>, &#123;</span><br><span class="line">      firstName: <span class="string">&#x27;foo&#x27;</span>,</span><br><span class="line">      lastName: <span class="string">&#x27;bar&#x27;</span></span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      testHeaderValue(request.requestHeaders, <span class="string">&#x27;Content-Type&#x27;</span>, <span class="string">&#x27;application/json;charset=utf-8&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should remove content-type if data is empty&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    axios.post(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      testHeaderValue(request.requestHeaders, <span class="string">&#x27;Content-Type&#x27;</span>, <span class="literal">undefined</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should preserve content-type if data is false&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    axios.post(<span class="string">&#x27;/foo&#x27;</span>, <span class="literal">false</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      testHeaderValue(request.requestHeaders, <span class="string">&#x27;Content-Type&#x27;</span>, <span class="string">&#x27;application/x-www-form-urlencoded&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should remove content-type if data is FormData&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> data = <span class="keyword">new</span> FormData()</span><br><span class="line">    data.append(<span class="string">&#x27;foo&#x27;</span>, <span class="string">&#x27;bar&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    axios.post(<span class="string">&#x27;/foo&#x27;</span>, data)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      testHeaderValue(request.requestHeaders, <span class="string">&#x27;Content-Type&#x27;</span>, <span class="literal">undefined</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>内部定义了 <code>testHeaderValue</code> 辅助函数，用于测试 <code>headers</code> 是否存在某个 <code>header name</code> 下的某个值。</p>
<p>至此我们完成了 <code>ts-axios</code> 库 <code>headers</code> 模块相关业务逻辑的测试，下一节课我们会对 <code>Axios</code> 的实例做测试。</p>
<h1 id="32-Axios-实例模块单元测试"><a href="#32-Axios-实例模块单元测试" class="headerlink" title="32.Axios 实例模块单元测试"></a>32.Axios 实例模块单元测试</h1><p><code>ts-axios</code> 提供了 <code>axios.create</code> 静态方法，返回一个 <code>instance</code> 实例，我们需要对这个模块做测试。</p>
<h2 id="32-1-测试代码编写"><a href="#32-1-测试代码编写" class="headerlink" title="32.1 测试代码编写"></a>32.1 测试代码编写</h2><p><code>test/instance.spec.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios, &#123; AxiosRequestConfig, AxiosResponse &#125; <span class="keyword">from</span> <span class="string">&#x27;../src/index&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; getAjaxRequest &#125; <span class="keyword">from</span> <span class="string">&#x27;./helper&#x27;</span></span><br><span class="line"></span><br><span class="line">describe(<span class="string">&#x27;instance&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  beforeEach(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    jasmine.Ajax.install()</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  afterEach(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    jasmine.Ajax.uninstall()</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should make a http request without verb helper&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> instance = axios.create()</span><br><span class="line"></span><br><span class="line">    instance(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.url).toBe(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should make a http request&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> instance = axios.create()</span><br><span class="line"></span><br><span class="line">    instance.get(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.url).toBe(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line">      expect(request.method).toBe(<span class="string">&#x27;GET&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should make a post request&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> instance = axios.create()</span><br><span class="line"></span><br><span class="line">    instance.post(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.method).toBe(<span class="string">&#x27;POST&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should make a put request&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> instance = axios.create()</span><br><span class="line"></span><br><span class="line">    instance.put(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.method).toBe(<span class="string">&#x27;PUT&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should make a patch request&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> instance = axios.create()</span><br><span class="line"></span><br><span class="line">    instance.patch(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.method).toBe(<span class="string">&#x27;PATCH&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should make a options request&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> instance = axios.create()</span><br><span class="line"></span><br><span class="line">    instance.options(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.method).toBe(<span class="string">&#x27;OPTIONS&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should make a delete request&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> instance = axios.create()</span><br><span class="line"></span><br><span class="line">    instance.delete(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.method).toBe(<span class="string">&#x27;DELETE&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should make a head request&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> instance = axios.create()</span><br><span class="line"></span><br><span class="line">    instance.head(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.method).toBe(<span class="string">&#x27;HEAD&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should use instance options&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> instance = axios.create(&#123; <span class="attr">timeout</span>: <span class="number">1000</span> &#125;)</span><br><span class="line"></span><br><span class="line">    instance.get(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.timeout).toBe(<span class="number">1000</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should have defaults.headers&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> instance = axios.create(&#123; <span class="attr">baseURL</span>: <span class="string">&#x27;https://api.example.com&#x27;</span> &#125;)</span><br><span class="line"></span><br><span class="line">    expect(<span class="keyword">typeof</span> instance.defaults.headers).toBe(<span class="string">&#x27;object&#x27;</span>)</span><br><span class="line">    expect(<span class="keyword">typeof</span> instance.defaults.headers.common).toBe(<span class="string">&#x27;object&#x27;</span>)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should have interceptors on the instance&#x27;</span>, <span class="function"><span class="params">done</span> =&gt;</span> &#123;</span><br><span class="line">    axios.interceptors.request.use(<span class="function"><span class="params">config</span> =&gt;</span> &#123;</span><br><span class="line">      config.timeout = <span class="number">2000</span></span><br><span class="line">      <span class="keyword">return</span> config</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> instance = axios.create()</span><br><span class="line"></span><br><span class="line">    instance.interceptors.request.use(<span class="function"><span class="params">config</span> =&gt;</span> &#123;</span><br><span class="line">      config.withCredentials = <span class="literal">true</span></span><br><span class="line">      <span class="keyword">return</span> config</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> response: AxiosResponse</span><br><span class="line">    instance.get(<span class="string">&#x27;/foo&#x27;</span>).then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">      response = res</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      request.respondWith(&#123;</span><br><span class="line">        status: <span class="number">200</span></span><br><span class="line">      &#125;)</span><br><span class="line"></span><br><span class="line">      <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        expect(response.config.timeout).toEqual(<span class="number">0</span>)</span><br><span class="line">        expect(response.config.withCredentials).toEqual(<span class="literal">true</span>)</span><br><span class="line">        done()</span><br><span class="line">      &#125;, <span class="number">100</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should get the computed uri&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> fakeConfig: AxiosRequestConfig = &#123;</span><br><span class="line">      baseURL: <span class="string">&#x27;https://www.baidu.com/&#x27;</span>,</span><br><span class="line">      url: <span class="string">&#x27;/user/12345&#x27;</span>,</span><br><span class="line">      params: &#123;</span><br><span class="line">        idClient: <span class="number">1</span>,</span><br><span class="line">        idTest: <span class="number">2</span>,</span><br><span class="line">        testString: <span class="string">&#x27;thisIsATest&#x27;</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    expect(axios.getUri(fakeConfig)).toBe(</span><br><span class="line">      <span class="string">&#x27;https://www.baidu.com/user/12345?idClient=1&amp;idTest=2&amp;testString=thisIsATest&#x27;</span></span><br><span class="line">    )</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>至此我们完成了 <code>ts-axios</code> 库 <code>Axios</code> 实例模块相关业务逻辑的测试，下一节课我们会对拦截器模块做测试。</p>
<h1 id="33-拦截器模块单元测试"><a href="#33-拦截器模块单元测试" class="headerlink" title="33.拦截器模块单元测试"></a>33.拦截器模块单元测试</h1><p>拦截器是 <code>ts-axios</code> 库一个非常实用的功能，接下来我们来编写它的测试代码。</p>
<h2 id="33-1-测试代码编写"><a href="#33-1-测试代码编写" class="headerlink" title="33.1 测试代码编写"></a>33.1 测试代码编写</h2><p><code>test/interceptor.spec.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios, &#123; AxiosRequestConfig, AxiosResponse &#125; <span class="keyword">from</span> <span class="string">&#x27;../src/index&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; getAjaxRequest &#125; <span class="keyword">from</span> <span class="string">&#x27;./helper&#x27;</span></span><br><span class="line"></span><br><span class="line">describe(<span class="string">&#x27;interceptors&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  beforeEach(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    jasmine.Ajax.install()</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  afterEach(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    jasmine.Ajax.uninstall()</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should add a request interceptor&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> instance = axios.create()</span><br><span class="line"></span><br><span class="line">    instance.interceptors.request.use(<span class="function">(<span class="params">config: AxiosRequestConfig</span>) =&gt;</span> &#123;</span><br><span class="line">      config.headers.test = <span class="string">&#x27;added by interceptor&#x27;</span></span><br><span class="line">      <span class="keyword">return</span> config</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    instance(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.requestHeaders.test).toBe(<span class="string">&#x27;added by interceptor&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should add a request interceptor that returns a new config object&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> instance = axios.create()</span><br><span class="line"></span><br><span class="line">    instance.interceptors.request.use(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> &#123;</span><br><span class="line">        url: <span class="string">&#x27;/bar&#x27;</span>,</span><br><span class="line">        method: <span class="string">&#x27;post&#x27;</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    instance(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.method).toBe(<span class="string">&#x27;POST&#x27;</span>)</span><br><span class="line">      expect(request.url).toBe(<span class="string">&#x27;/bar&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should add a request interceptor that returns a promise&#x27;</span>, <span class="function"><span class="params">done</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> instance = axios.create()</span><br><span class="line"></span><br><span class="line">    instance.interceptors.request.use(<span class="function">(<span class="params">config: AxiosRequestConfig</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="params">resolve</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">          config.headers.async = <span class="string">&#x27;promise&#x27;</span></span><br><span class="line">          resolve(config)</span><br><span class="line">        &#125;, <span class="number">10</span>)</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    instance(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">        expect(request.requestHeaders.async).toBe(<span class="string">&#x27;promise&#x27;</span>)</span><br><span class="line">        done()</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;, <span class="number">100</span>)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should add multiple request interceptors&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> instance = axios.create()</span><br><span class="line"></span><br><span class="line">    instance.interceptors.request.use(<span class="function"><span class="params">config</span> =&gt;</span> &#123;</span><br><span class="line">      config.headers.test1 = <span class="string">&#x27;1&#x27;</span></span><br><span class="line">      <span class="keyword">return</span> config</span><br><span class="line">    &#125;)</span><br><span class="line">    instance.interceptors.request.use(<span class="function"><span class="params">config</span> =&gt;</span> &#123;</span><br><span class="line">      config.headers.test2 = <span class="string">&#x27;2&#x27;</span></span><br><span class="line">      <span class="keyword">return</span> config</span><br><span class="line">    &#125;)</span><br><span class="line">    instance.interceptors.request.use(<span class="function"><span class="params">config</span> =&gt;</span> &#123;</span><br><span class="line">      config.headers.test3 = <span class="string">&#x27;3&#x27;</span></span><br><span class="line">      <span class="keyword">return</span> config</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    instance(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.requestHeaders.test1).toBe(<span class="string">&#x27;1&#x27;</span>)</span><br><span class="line">      expect(request.requestHeaders.test2).toBe(<span class="string">&#x27;2&#x27;</span>)</span><br><span class="line">      expect(request.requestHeaders.test3).toBe(<span class="string">&#x27;3&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should add a response interceptor&#x27;</span>, <span class="function"><span class="params">done</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> response: AxiosResponse</span><br><span class="line">    <span class="keyword">const</span> instance = axios.create()</span><br><span class="line"></span><br><span class="line">    instance.interceptors.response.use(<span class="function"><span class="params">data</span> =&gt;</span> &#123;</span><br><span class="line">      data.data = data.data + <span class="string">&#x27; - modified by interceptor&#x27;</span></span><br><span class="line">      <span class="keyword">return</span> data</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    instance(<span class="string">&#x27;/foo&#x27;</span>).then(<span class="function"><span class="params">data</span> =&gt;</span> &#123;</span><br><span class="line">      response = data</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      request.respondWith(&#123;</span><br><span class="line">        status: <span class="number">200</span>,</span><br><span class="line">        responseText: <span class="string">&#x27;OK&#x27;</span></span><br><span class="line">      &#125;)</span><br><span class="line"></span><br><span class="line">      <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        expect(response.data).toBe(<span class="string">&#x27;OK - modified by interceptor&#x27;</span>)</span><br><span class="line">        done()</span><br><span class="line">      &#125;, <span class="number">100</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should add a response interceptor that returns a new data object&#x27;</span>, <span class="function"><span class="params">done</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> response: AxiosResponse</span><br><span class="line">    <span class="keyword">const</span> instance = axios.create()</span><br><span class="line"></span><br><span class="line">    instance.interceptors.response.use(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> &#123;</span><br><span class="line">        data: <span class="string">&#x27;stuff&#x27;</span>,</span><br><span class="line">        headers: <span class="literal">null</span>,</span><br><span class="line">        status: <span class="number">500</span>,</span><br><span class="line">        statusText: <span class="string">&#x27;ERR&#x27;</span>,</span><br><span class="line">        request: <span class="literal">null</span>,</span><br><span class="line">        config: &#123;&#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    instance(<span class="string">&#x27;/foo&#x27;</span>).then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">      response = res</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      request.respondWith(&#123;</span><br><span class="line">        status: <span class="number">200</span>,</span><br><span class="line">        responseText: <span class="string">&#x27;OK&#x27;</span></span><br><span class="line">      &#125;)</span><br><span class="line"></span><br><span class="line">      <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        expect(response.data).toBe(<span class="string">&#x27;stuff&#x27;</span>)</span><br><span class="line">        expect(response.headers).toBeNull()</span><br><span class="line">        expect(response.status).toBe(<span class="number">500</span>)</span><br><span class="line">        expect(response.statusText).toBe(<span class="string">&#x27;ERR&#x27;</span>)</span><br><span class="line">        expect(response.request).toBeNull()</span><br><span class="line">        expect(response.config).toEqual(&#123;&#125;)</span><br><span class="line">        done()</span><br><span class="line">      &#125;, <span class="number">100</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should add a response interceptor that returns a promise&#x27;</span>, <span class="function"><span class="params">done</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> response: AxiosResponse</span><br><span class="line">    <span class="keyword">const</span> instance = axios.create()</span><br><span class="line"></span><br><span class="line">    instance.interceptors.response.use(<span class="function"><span class="params">data</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="params">resolve</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="comment">// do something async</span></span><br><span class="line">        <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">          data.data = <span class="string">&#x27;you have been promised!&#x27;</span></span><br><span class="line">          resolve(data)</span><br><span class="line">        &#125;, <span class="number">10</span>)</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    instance(<span class="string">&#x27;/foo&#x27;</span>).then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">      response = res</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      request.respondWith(&#123;</span><br><span class="line">        status: <span class="number">200</span>,</span><br><span class="line">        responseText: <span class="string">&#x27;OK&#x27;</span></span><br><span class="line">      &#125;)</span><br><span class="line"></span><br><span class="line">      <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        expect(response.data).toBe(<span class="string">&#x27;you have been promised!&#x27;</span>)</span><br><span class="line">        done()</span><br><span class="line">      &#125;, <span class="number">100</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should add multiple response interceptors&#x27;</span>, <span class="function"><span class="params">done</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> response: AxiosResponse</span><br><span class="line">    <span class="keyword">const</span> instance = axios.create()</span><br><span class="line"></span><br><span class="line">    instance.interceptors.response.use(<span class="function"><span class="params">data</span> =&gt;</span> &#123;</span><br><span class="line">      data.data = data.data + <span class="string">&#x27;1&#x27;</span></span><br><span class="line">      <span class="keyword">return</span> data</span><br><span class="line">    &#125;)</span><br><span class="line">    instance.interceptors.response.use(<span class="function"><span class="params">data</span> =&gt;</span> &#123;</span><br><span class="line">      data.data = data.data + <span class="string">&#x27;2&#x27;</span></span><br><span class="line">      <span class="keyword">return</span> data</span><br><span class="line">    &#125;)</span><br><span class="line">    instance.interceptors.response.use(<span class="function"><span class="params">data</span> =&gt;</span> &#123;</span><br><span class="line">      data.data = data.data + <span class="string">&#x27;3&#x27;</span></span><br><span class="line">      <span class="keyword">return</span> data</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    instance(<span class="string">&#x27;/foo&#x27;</span>).then(<span class="function"><span class="params">data</span> =&gt;</span> &#123;</span><br><span class="line">      response = data</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      request.respondWith(&#123;</span><br><span class="line">        status: <span class="number">200</span>,</span><br><span class="line">        responseText: <span class="string">&#x27;OK&#x27;</span></span><br><span class="line">      &#125;)</span><br><span class="line"></span><br><span class="line">      <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        expect(response.data).toBe(<span class="string">&#x27;OK123&#x27;</span>)</span><br><span class="line">        done()</span><br><span class="line">      &#125;, <span class="number">100</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should allow removing interceptors&#x27;</span>, <span class="function"><span class="params">done</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> response: AxiosResponse</span><br><span class="line">    <span class="keyword">let</span> intercept</span><br><span class="line">    <span class="keyword">const</span> instance = axios.create()</span><br><span class="line"></span><br><span class="line">    instance.interceptors.response.use(<span class="function"><span class="params">data</span> =&gt;</span> &#123;</span><br><span class="line">      data.data = data.data + <span class="string">&#x27;1&#x27;</span></span><br><span class="line">      <span class="keyword">return</span> data</span><br><span class="line">    &#125;)</span><br><span class="line">    intercept = instance.interceptors.response.use(<span class="function"><span class="params">data</span> =&gt;</span> &#123;</span><br><span class="line">      data.data = data.data + <span class="string">&#x27;2&#x27;</span></span><br><span class="line">      <span class="keyword">return</span> data</span><br><span class="line">    &#125;)</span><br><span class="line">    instance.interceptors.response.use(<span class="function"><span class="params">data</span> =&gt;</span> &#123;</span><br><span class="line">      data.data = data.data + <span class="string">&#x27;3&#x27;</span></span><br><span class="line">      <span class="keyword">return</span> data</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    instance.interceptors.response.eject(intercept)</span><br><span class="line">    instance.interceptors.response.eject(<span class="number">5</span>)</span><br><span class="line"></span><br><span class="line">    instance(<span class="string">&#x27;/foo&#x27;</span>).then(<span class="function"><span class="params">data</span> =&gt;</span> &#123;</span><br><span class="line">      response = data</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      request.respondWith(&#123;</span><br><span class="line">        status: <span class="number">200</span>,</span><br><span class="line">        responseText: <span class="string">&#x27;OK&#x27;</span></span><br><span class="line">      &#125;)</span><br><span class="line"></span><br><span class="line">      <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        expect(response.data).toBe(<span class="string">&#x27;OK13&#x27;</span>)</span><br><span class="line">        done()</span><br><span class="line">      &#125;, <span class="number">100</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>运行测试后我们发现在测试用例 <code>should add a request interceptor that returns a new config object</code> 报错了，是代码运行的报错，而不是测试期望结果的报错，顺着报错信息，我们可以找到报错原因。</p>
<p>在 <code>core/xhr.ts</code> 中，执行到 <code>processHeaders</code> 中的 <code>Object.keys(headers).forEach</code> 代码报错，因为我们在拦截器对请求配置做了修改，导致 <code>headers</code> 为空，所以报错。</p>
<p>于是我们在解构赋值 <code>headers</code> 的时候，给它添加默认值即可。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  headers = &#123;&#125;</span><br><span class="line">&#125; = config</span><br></pre></td></tr></table></figure>

<p>再次运行测试，发现全部测试通过。</p>
<p>至此，我们完成了 <code>ts-axios</code> 库对拦截器模块的单元测试，下节课我们来测试 <code>mergeConfig</code> 模块的业务逻辑。</p>
<h1 id="34-mergeConfig-模块单元测试"><a href="#34-mergeConfig-模块单元测试" class="headerlink" title="34.mergeConfig 模块单元测试"></a>34.mergeConfig 模块单元测试</h1><p>合并配置是 <code>ts-axios</code> 核心流程中非常重要的一个环节，我们需要为它的各种情况去编写测试。</p>
<h2 id="34-1-测试代码编写"><a href="#34-1-测试代码编写" class="headerlink" title="34.1 测试代码编写"></a>34.1 测试代码编写</h2><p><code>test/mergeConfig.spec.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;../src/index&#x27;</span></span><br><span class="line"><span class="keyword">import</span> mergeConfig <span class="keyword">from</span> <span class="string">&#x27;../src/core/mergeConfig&#x27;</span></span><br><span class="line"></span><br><span class="line">describe(<span class="string">&#x27;mergeConfig&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> defaults = axios.defaults</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should accept undefined for second argument&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    expect(mergeConfig(defaults, <span class="literal">undefined</span>)).toEqual(defaults)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should accept an object for second argument&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    expect(mergeConfig(defaults, &#123;&#125;)).toEqual(defaults)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should not leave references&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> merged = mergeConfig(defaults, &#123;&#125;)</span><br><span class="line">    expect(merged).not.toBe(defaults)</span><br><span class="line">    expect(merged.headers).not.toBe(defaults.headers)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should allow setting request options&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> config = &#123;</span><br><span class="line">      url: <span class="string">&#x27;__sample url__&#x27;</span>,</span><br><span class="line">      params: <span class="string">&#x27;__sample params__&#x27;</span>,</span><br><span class="line">      data: &#123; <span class="attr">foo</span>: <span class="literal">true</span> &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">const</span> merged = mergeConfig(defaults, config)</span><br><span class="line">    expect(merged.url).toBe(config.url)</span><br><span class="line">    expect(merged.params).toBe(config.params)</span><br><span class="line">    expect(merged.data).toEqual(config.data)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should not inherit request options&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> localDefaults = &#123;</span><br><span class="line">      url: <span class="string">&#x27;__sample url__&#x27;</span>,</span><br><span class="line">      params: <span class="string">&#x27;__sample params__&#x27;</span>,</span><br><span class="line">      data: &#123; <span class="attr">foo</span>: <span class="literal">true</span> &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">const</span> merged = mergeConfig(localDefaults, &#123;&#125;)</span><br><span class="line">    expect(merged.url).toBeUndefined()</span><br><span class="line">    expect(merged.params).toBeUndefined()</span><br><span class="line">    expect(merged.data).toBeUndefined()</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should return default headers if pass config2 with undefined&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    expect(</span><br><span class="line">      mergeConfig(</span><br><span class="line">        &#123;</span><br><span class="line">          headers: <span class="string">&#x27;x-mock-header&#x27;</span></span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="literal">undefined</span></span><br><span class="line">      )</span><br><span class="line">    ).toEqual(&#123;</span><br><span class="line">      headers: <span class="string">&#x27;x-mock-header&#x27;</span></span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should merge auth, headers with defaults&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    expect(</span><br><span class="line">      mergeConfig(</span><br><span class="line">        &#123;</span><br><span class="line">          auth: <span class="literal">undefined</span></span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">          auth: &#123;</span><br><span class="line">            username: <span class="string">&#x27;foo&#x27;</span>,</span><br><span class="line">            password: <span class="string">&#x27;test&#x27;</span></span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      )</span><br><span class="line">    ).toEqual(&#123;</span><br><span class="line">      auth: &#123;</span><br><span class="line">        username: <span class="string">&#x27;foo&#x27;</span>,</span><br><span class="line">        password: <span class="string">&#x27;test&#x27;</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">    expect(</span><br><span class="line">      mergeConfig(</span><br><span class="line">        &#123;</span><br><span class="line">          auth: &#123;</span><br><span class="line">            username: <span class="string">&#x27;foo&#x27;</span>,</span><br><span class="line">            password: <span class="string">&#x27;test&#x27;</span></span><br><span class="line">          &#125;</span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">          auth: &#123;</span><br><span class="line">            username: <span class="string">&#x27;baz&#x27;</span>,</span><br><span class="line">            password: <span class="string">&#x27;foobar&#x27;</span></span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      )</span><br><span class="line">    ).toEqual(&#123;</span><br><span class="line">      auth: &#123;</span><br><span class="line">        username: <span class="string">&#x27;baz&#x27;</span>,</span><br><span class="line">        password: <span class="string">&#x27;foobar&#x27;</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should overwrite auth, headers with a non-object value&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    expect(</span><br><span class="line">      mergeConfig(</span><br><span class="line">        &#123;</span><br><span class="line">          headers: &#123;</span><br><span class="line">            common: &#123;</span><br><span class="line">              Accept: <span class="string">&#x27;application/json, text/plain, */*&#x27;</span></span><br><span class="line">            &#125;</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">          headers: <span class="literal">null</span></span><br><span class="line">        &#125;</span><br><span class="line">      )</span><br><span class="line">    ).toEqual(&#123;</span><br><span class="line">      headers: <span class="literal">null</span></span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should allow setting other options&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> merged = mergeConfig(defaults, &#123;</span><br><span class="line">      timeout: <span class="number">123</span></span><br><span class="line">    &#125;)</span><br><span class="line">    expect(merged.timeout).toBe(<span class="number">123</span>)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>运行测试后我们发现 <code>mergeConfig.ts</code> 文件的分支覆盖率并未达到 100%，提示是 23 行，打开文件后发现最后一个 <code>else</code> 逻辑并未走到，也就是 <code>val1</code> 为 <code>undefined</code> 的情况。但实际上即使 <code>val1</code> 为 <code>undefined</code>，我们也是返回 <code>undefined</code>，也就是返回 <code>val1</code>，所以这块代码的逻辑可以优化。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">deepMergeStrat</span>(<span class="params">val1: <span class="built_in">any</span>, val2: <span class="built_in">any</span></span>): <span class="title">any</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (isPlainObject(val2)) &#123;</span><br><span class="line">    <span class="keyword">return</span> deepMerge(val1, val2)</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">typeof</span> val2 !== <span class="string">&#x27;undefined&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> val2</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (isPlainObject(val1)) &#123;</span><br><span class="line">    <span class="keyword">return</span> deepMerge(val1)</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> val1</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>2 个分支可以合并到一个分支，这样我们再次跑测试，分支覆盖率就可以达到 100% 了。</p>
<p>至此我们完成了 <code>ts-axios</code> 库对 <code>mergeConfig</code> 模块的测试，下一节课我们来测试取消模块相关代码。</p>
<h1 id="35-请求取消模块单元测试"><a href="#35-请求取消模块单元测试" class="headerlink" title="35.请求取消模块单元测试"></a>35.请求取消模块单元测试</h1><p>请求取消模块是 <code>ts-axios</code> 库核心流程其中一个分支，也是非常重要的模块，我们将从基础库和业务流程模块 2 个方面去编写单元测试。</p>
<h2 id="35-1-Cancel-类单元测试"><a href="#35-1-Cancel-类单元测试" class="headerlink" title="35.1 Cancel 类单元测试"></a>35.1 Cancel 类单元测试</h2><p><code>cancel/Cancel.spec.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Cancel, &#123; isCancel &#125; <span class="keyword">from</span> <span class="string">&#x27;../../src/cancel/Cancel&#x27;</span></span><br><span class="line"></span><br><span class="line">describe(<span class="string">&#x27;cancel:Cancel&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  test(<span class="string">&#x27;should returns correct result when message is specified&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> cancel = <span class="keyword">new</span> Cancel(<span class="string">&#x27;Operation has been canceled.&#x27;</span>)</span><br><span class="line">    expect(cancel.message).toBe(<span class="string">&#x27;Operation has been canceled.&#x27;</span>)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should returns true if value is a Cancel&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    expect(isCancel(<span class="keyword">new</span> Cancel())).toBeTruthy()</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should returns false if value is not a Cancel&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    expect(isCancel(&#123; <span class="attr">foo</span>: <span class="string">&#x27;bar&#x27;</span> &#125;)).toBeFalsy()</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<h2 id="35-2-CancelToken-类单元测试"><a href="#35-2-CancelToken-类单元测试" class="headerlink" title="35.2 CancelToken 类单元测试"></a>35.2 CancelToken 类单元测试</h2><p><code>cancel/CancelToken.spec.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> CancelToken <span class="keyword">from</span> <span class="string">&#x27;../../src/cancel/CancelToken&#x27;</span></span><br><span class="line"><span class="keyword">import</span> Cancel <span class="keyword">from</span> <span class="string">&#x27;../../src/cancel/Cancel&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; Canceler &#125; <span class="keyword">from</span> <span class="string">&#x27;../../src/types&#x27;</span></span><br><span class="line"></span><br><span class="line">describe(<span class="string">&#x27;CancelToken&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  describe(<span class="string">&#x27;reason&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    test(<span class="string">&#x27;should returns a Cancel if cancellation has been requested&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">let</span> cancel: Canceler</span><br><span class="line">      <span class="keyword">let</span> token = <span class="keyword">new</span> CancelToken(<span class="function"><span class="params">c</span> =&gt;</span> &#123;</span><br><span class="line">        cancel = c</span><br><span class="line">      &#125;)</span><br><span class="line">      cancel!(<span class="string">&#x27;Operation has been canceled.&#x27;</span>)</span><br><span class="line">      expect(token.reason).toEqual(expect.any(Cancel))</span><br><span class="line">      expect(token.reason!.message).toBe(<span class="string">&#x27;Operation has been canceled.&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should has no side effect if call cancellation for multi times&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">let</span> cancel: Canceler</span><br><span class="line">      <span class="keyword">let</span> token = <span class="keyword">new</span> CancelToken(<span class="function"><span class="params">c</span> =&gt;</span> &#123;</span><br><span class="line">        cancel = c</span><br><span class="line">      &#125;)</span><br><span class="line">      cancel!(<span class="string">&#x27;Operation has been canceled.&#x27;</span>)</span><br><span class="line">      cancel!(<span class="string">&#x27;Operation has been canceled.&#x27;</span>)</span><br><span class="line">      expect(token.reason).toEqual(expect.any(Cancel))</span><br><span class="line">      expect(token.reason!.message).toBe(<span class="string">&#x27;Operation has been canceled.&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should returns undefined if cancellation has not been requested&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> token = <span class="keyword">new</span> CancelToken(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="comment">// do nothing</span></span><br><span class="line">      &#125;)</span><br><span class="line">      expect(token.reason).toBeUndefined()</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  describe(<span class="string">&#x27;promise&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    test(<span class="string">&#x27;should returns a Promise that resolves when cancellation is requested&#x27;</span>, <span class="function"><span class="params">done</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">let</span> cancel: Canceler</span><br><span class="line">      <span class="keyword">const</span> token = <span class="keyword">new</span> CancelToken(<span class="function"><span class="params">c</span> =&gt;</span> &#123;</span><br><span class="line">        cancel = c</span><br><span class="line">      &#125;)</span><br><span class="line">      token.promise.then(<span class="function"><span class="params">value</span> =&gt;</span> &#123;</span><br><span class="line">        expect(value).toEqual(expect.any(Cancel))</span><br><span class="line">        expect(value.message).toBe(<span class="string">&#x27;Operation has been canceled.&#x27;</span>)</span><br><span class="line">        done()</span><br><span class="line">      &#125;)</span><br><span class="line">      cancel!(<span class="string">&#x27;Operation has been canceled.&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  describe(<span class="string">&#x27;throwIfRequested&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    test(<span class="string">&#x27;should throws if cancellation has been requested&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">let</span> cancel: Canceler</span><br><span class="line">      <span class="keyword">const</span> token = <span class="keyword">new</span> CancelToken(<span class="function"><span class="params">c</span> =&gt;</span> &#123;</span><br><span class="line">        cancel = c</span><br><span class="line">      &#125;)</span><br><span class="line">      cancel!(<span class="string">&#x27;Operation has been canceled.&#x27;</span>)</span><br><span class="line">      <span class="keyword">try</span> &#123;</span><br><span class="line">        token.throwIfRequested()</span><br><span class="line">        fail(<span class="string">&#x27;Expected throwIfRequested to throw.&#x27;</span>)</span><br><span class="line">      &#125; <span class="keyword">catch</span> (thrown) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!(thrown <span class="keyword">instanceof</span> Cancel)) &#123;</span><br><span class="line">          fail(<span class="string">&#x27;Expected throwIfRequested to throw a Cancel, but test threw &#x27;</span> + thrown + <span class="string">&#x27;.&#x27;</span>)</span><br><span class="line">        &#125;</span><br><span class="line">        expect(thrown.message).toBe(<span class="string">&#x27;Operation has been canceled.&#x27;</span>)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;should does not throw if cancellation has not been requested&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> token = <span class="keyword">new</span> CancelToken(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="comment">// do nothing</span></span><br><span class="line">      &#125;)</span><br><span class="line">      token.throwIfRequested()</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  describe(<span class="string">&#x27;source&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    test(<span class="string">&#x27;should returns an object containing token and cancel function&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> source = CancelToken.source()</span><br><span class="line">      expect(source.token).toEqual(expect.any(CancelToken))</span><br><span class="line">      expect(source.cancel).toEqual(expect.any(<span class="built_in">Function</span>))</span><br><span class="line">      expect(source.token.reason).toBeUndefined()</span><br><span class="line">      source.cancel(<span class="string">&#x27;Operation has been canceled.&#x27;</span>)</span><br><span class="line">      expect(source.token.reason).toEqual(expect.any(Cancel))</span><br><span class="line">      expect(source.token.reason!.message).toBe(<span class="string">&#x27;Operation has been canceled.&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>注意，这里我们使用了 <code>fail</code> 函数表示一个测试的失败，这个并未在 Jest 文档中体现，但它是一个可以用的 API。</p>
<h2 id="35-3-Cancel-业务逻辑单元测试"><a href="#35-3-Cancel-业务逻辑单元测试" class="headerlink" title="35.3 Cancel 业务逻辑单元测试"></a>35.3 Cancel 业务逻辑单元测试</h2><p><code>cancel.spec.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;../src/index&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; getAjaxRequest &#125; <span class="keyword">from</span> <span class="string">&#x27;./helper&#x27;</span></span><br><span class="line"></span><br><span class="line">describe(<span class="string">&#x27;cancel&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> CancelToken = axios.CancelToken</span><br><span class="line">  <span class="keyword">const</span> Cancel = axios.Cancel</span><br><span class="line"></span><br><span class="line">  beforeEach(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    jasmine.Ajax.install()</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  afterEach(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    jasmine.Ajax.uninstall()</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  describe(<span class="string">&#x27;when called before sending request&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    test(<span class="string">&#x27;should rejects Promise with a Cancel object&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> source = CancelToken.source()</span><br><span class="line">      source.cancel(<span class="string">&#x27;Operation has been canceled.&#x27;</span>)</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> axios</span><br><span class="line">        .get(<span class="string">&#x27;/foo&#x27;</span>, &#123;</span><br><span class="line">          cancelToken: source.token</span><br><span class="line">        &#125;)</span><br><span class="line">        .catch(<span class="function"><span class="params">reason</span> =&gt;</span> &#123;</span><br><span class="line">          expect(reason).toEqual(expect.any(Cancel))</span><br><span class="line">          expect(reason.message).toBe(<span class="string">&#x27;Operation has been canceled.&#x27;</span>)</span><br><span class="line">        &#125;)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  describe(<span class="string">&#x27;when called after request has been sent&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    test(<span class="string">&#x27;should rejects Promise with a Cancel object&#x27;</span>, <span class="function"><span class="params">done</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> source = CancelToken.source()</span><br><span class="line">      axios</span><br><span class="line">        .get(<span class="string">&#x27;/foo/bar&#x27;</span>, &#123;</span><br><span class="line">          cancelToken: source.token</span><br><span class="line">        &#125;)</span><br><span class="line">        .catch(<span class="function"><span class="params">reason</span> =&gt;</span> &#123;</span><br><span class="line">          expect(reason).toEqual(expect.any(Cancel))</span><br><span class="line">          expect(reason.message).toBe(<span class="string">&#x27;Operation has been canceled.&#x27;</span>)</span><br><span class="line">          done()</span><br><span class="line">        &#125;)</span><br><span class="line"></span><br><span class="line">      getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">        source.cancel(<span class="string">&#x27;Operation has been canceled.&#x27;</span>)</span><br><span class="line">        <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">          request.respondWith(&#123;</span><br><span class="line">            status: <span class="number">200</span>,</span><br><span class="line">            responseText: <span class="string">&#x27;OK&#x27;</span></span><br><span class="line">          &#125;)</span><br><span class="line">        &#125;, <span class="number">100</span>)</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    test(<span class="string">&#x27;calls abort on request object&#x27;</span>, <span class="function"><span class="params">done</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> source = CancelToken.source()</span><br><span class="line">      <span class="keyword">let</span> request: <span class="built_in">any</span></span><br><span class="line">      axios</span><br><span class="line">        .get(<span class="string">&#x27;/foo/bar&#x27;</span>, &#123;</span><br><span class="line">          cancelToken: source.token</span><br><span class="line">        &#125;)</span><br><span class="line">        .catch(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">          expect(request.statusText).toBe(<span class="string">&#x27;abort&#x27;</span>)</span><br><span class="line">          done()</span><br><span class="line">        &#125;)</span><br><span class="line"></span><br><span class="line">      getAjaxRequest().then(<span class="function"><span class="params">req</span> =&gt;</span> &#123;</span><br><span class="line">        source.cancel()</span><br><span class="line">        request = req</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  describe(<span class="string">&#x27;when called after response has been received&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    test(<span class="string">&#x27;should not cause unhandled rejection&#x27;</span>, <span class="function"><span class="params">done</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> source = CancelToken.source()</span><br><span class="line">      axios</span><br><span class="line">        .get(<span class="string">&#x27;/foo&#x27;</span>, &#123;</span><br><span class="line">          cancelToken: source.token</span><br><span class="line">        &#125;)</span><br><span class="line">        .then(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">          <span class="built_in">window</span>.addEventListener(<span class="string">&#x27;unhandledrejection&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">            done.fail(<span class="string">&#x27;Unhandled rejection.&#x27;</span>)</span><br><span class="line">          &#125;)</span><br><span class="line">          source.cancel()</span><br><span class="line">          <span class="built_in">setTimeout</span>(done, <span class="number">100</span>)</span><br><span class="line">        &#125;)</span><br><span class="line"></span><br><span class="line">      getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">        request.respondWith(&#123;</span><br><span class="line">          status: <span class="number">200</span>,</span><br><span class="line">          responseText: <span class="string">&#x27;OK&#x27;</span></span><br><span class="line">        &#125;)</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>注意这里我们使用了 <code>done.fail</code> 表示了一个异常的结束，这个并未在 Jest 文档中体现，但它是一个可以用的 API。</p>
<p>至此，我们完成了取消模块相关业务逻辑的单元测试，我们测试覆盖率达到了阈值，测试已经通过了。但是仍未达到我们的目标，还有很多 feature 是没有覆盖到的。接下来我们就完成剩余 feature 的编写单元测试。</p>
<h1 id="36-剩余模块单元测试"><a href="#36-剩余模块单元测试" class="headerlink" title="36.剩余模块单元测试"></a>36.剩余模块单元测试</h1><h2 id="36-1-defaults-模块单元测试"><a href="#36-1-defaults-模块单元测试" class="headerlink" title="36.1 defaults 模块单元测试"></a>36.1 defaults 模块单元测试</h2><p><code>defaults</code> 模块为请求配置提供了一些默认的属性和方法，我们需要为其编写单元测试。</p>
<p><code>test/defaults.spec.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios, &#123; AxiosTransformer &#125; <span class="keyword">from</span> <span class="string">&#x27;../src/index&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; getAjaxRequest &#125; <span class="keyword">from</span> <span class="string">&#x27;./helper&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; deepMerge &#125; <span class="keyword">from</span> <span class="string">&#x27;../src/helpers/util&#x27;</span></span><br><span class="line"></span><br><span class="line">describe(<span class="string">&#x27;defaults&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  beforeEach(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    jasmine.Ajax.install()</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  afterEach(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    jasmine.Ajax.uninstall()</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should transform request json&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    expect((axios.defaults.transformRequest <span class="keyword">as</span> AxiosTransformer[])[<span class="number">0</span>](&#123; <span class="attr">foo</span>: <span class="string">&#x27;bar&#x27;</span> &#125;)).toBe(<span class="string">&#x27;&#123;&quot;foo&quot;:&quot;bar&quot;&#125;&#x27;</span>)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should do nothing to request string&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    expect((axios.defaults.transformRequest <span class="keyword">as</span> AxiosTransformer[])[<span class="number">0</span>](<span class="string">&#x27;foo=bar&#x27;</span>)).toBe(<span class="string">&#x27;foo=bar&#x27;</span>)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should transform response json&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> data = (axios.defaults.transformResponse <span class="keyword">as</span> AxiosTransformer[])[<span class="number">0</span>](<span class="string">&#x27;&#123;&quot;foo&quot;:&quot;bar&quot;&#125;&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    expect(<span class="keyword">typeof</span> data).toBe(<span class="string">&#x27;object&#x27;</span>)</span><br><span class="line">    expect(data.foo).toBe(<span class="string">&#x27;bar&#x27;</span>)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should do nothing to response string&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    expect((axios.defaults.transformResponse <span class="keyword">as</span> AxiosTransformer[])[<span class="number">0</span>](<span class="string">&#x27;foo=bar&#x27;</span>)).toBe(<span class="string">&#x27;foo=bar&#x27;</span>)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should use global defaults config&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    axios(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.url).toBe(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should use modified defaults config&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    axios.defaults.baseURL = <span class="string">&#x27;http://example.com/&#x27;</span></span><br><span class="line"></span><br><span class="line">    axios(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.url).toBe(<span class="string">&#x27;http://example.com/foo&#x27;</span>)</span><br><span class="line">      <span class="keyword">delete</span> axios.defaults.baseURL</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should use request config&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    axios(<span class="string">&#x27;/foo&#x27;</span>, &#123;</span><br><span class="line">      baseURL: <span class="string">&#x27;http://www.example.com&#x27;</span></span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.url).toBe(<span class="string">&#x27;http://www.example.com/foo&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should use default config for custom instance&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> instance = axios.create(&#123;</span><br><span class="line">      xsrfCookieName: <span class="string">&#x27;CUSTOM-XSRF-TOKEN&#x27;</span>,</span><br><span class="line">      xsrfHeaderName: <span class="string">&#x27;X-CUSTOM-XSRF-TOKEN&#x27;</span></span><br><span class="line">    &#125;)</span><br><span class="line">    <span class="built_in">document</span>.cookie = instance.defaults.xsrfCookieName + <span class="string">&#x27;=foobarbaz&#x27;</span></span><br><span class="line"></span><br><span class="line">    instance.get(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.requestHeaders[instance.defaults.xsrfHeaderName!]).toBe(<span class="string">&#x27;foobarbaz&#x27;</span>)</span><br><span class="line">      <span class="built_in">document</span>.cookie =</span><br><span class="line">        instance.defaults.xsrfCookieName +</span><br><span class="line">        <span class="string">&#x27;=;expires=&#x27;</span> +</span><br><span class="line">        <span class="keyword">new</span> <span class="built_in">Date</span>(<span class="built_in">Date</span>.now() - <span class="number">86400000</span>).toUTCString()</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should use GET headers&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    axios.defaults.headers.get[<span class="string">&#x27;X-CUSTOM-HEADER&#x27;</span>] = <span class="string">&#x27;foo&#x27;</span></span><br><span class="line">    axios.get(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.requestHeaders[<span class="string">&#x27;X-CUSTOM-HEADER&#x27;</span>]).toBe(<span class="string">&#x27;foo&#x27;</span>)</span><br><span class="line">      <span class="keyword">delete</span> axios.defaults.headers.get[<span class="string">&#x27;X-CUSTOM-HEADER&#x27;</span>]</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should use POST headers&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    axios.defaults.headers.post[<span class="string">&#x27;X-CUSTOM-HEADER&#x27;</span>] = <span class="string">&#x27;foo&#x27;</span></span><br><span class="line">    axios.post(<span class="string">&#x27;/foo&#x27;</span>, &#123;&#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.requestHeaders[<span class="string">&#x27;X-CUSTOM-HEADER&#x27;</span>]).toBe(<span class="string">&#x27;foo&#x27;</span>)</span><br><span class="line">      <span class="keyword">delete</span> axios.defaults.headers.post[<span class="string">&#x27;X-CUSTOM-HEADER&#x27;</span>]</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should use header config&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> instance = axios.create(&#123;</span><br><span class="line">      headers: &#123;</span><br><span class="line">        common: &#123;</span><br><span class="line">          <span class="string">&#x27;X-COMMON-HEADER&#x27;</span>: <span class="string">&#x27;commonHeaderValue&#x27;</span></span><br><span class="line">        &#125;,</span><br><span class="line">        get: &#123;</span><br><span class="line">          <span class="string">&#x27;X-GET-HEADER&#x27;</span>: <span class="string">&#x27;getHeaderValue&#x27;</span></span><br><span class="line">        &#125;,</span><br><span class="line">        post: &#123;</span><br><span class="line">          <span class="string">&#x27;X-POST-HEADER&#x27;</span>: <span class="string">&#x27;postHeaderValue&#x27;</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    instance.get(<span class="string">&#x27;/foo&#x27;</span>, &#123;</span><br><span class="line">      headers: &#123;</span><br><span class="line">        <span class="string">&#x27;X-FOO-HEADER&#x27;</span>: <span class="string">&#x27;fooHeaderValue&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;X-BAR-HEADER&#x27;</span>: <span class="string">&#x27;barHeaderValue&#x27;</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.requestHeaders).toEqual(</span><br><span class="line">        deepMerge(axios.defaults.headers.common, axios.defaults.headers.get, &#123;</span><br><span class="line">          <span class="string">&#x27;X-COMMON-HEADER&#x27;</span>: <span class="string">&#x27;commonHeaderValue&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;X-GET-HEADER&#x27;</span>: <span class="string">&#x27;getHeaderValue&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;X-FOO-HEADER&#x27;</span>: <span class="string">&#x27;fooHeaderValue&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;X-BAR-HEADER&#x27;</span>: <span class="string">&#x27;barHeaderValue&#x27;</span></span><br><span class="line">        &#125;)</span><br><span class="line">      )</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should be used by custom instance if set before instance created&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    axios.defaults.baseURL = <span class="string">&#x27;http://example.org/&#x27;</span></span><br><span class="line">    <span class="keyword">const</span> instance = axios.create()</span><br><span class="line"></span><br><span class="line">    instance.get(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.url).toBe(<span class="string">&#x27;http://example.org/foo&#x27;</span>)</span><br><span class="line">      <span class="keyword">delete</span> axios.defaults.baseURL</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should not be used by custom instance if set after instance created&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> instance = axios.create()</span><br><span class="line">    axios.defaults.baseURL = <span class="string">&#x27;http://example.org/&#x27;</span></span><br><span class="line"></span><br><span class="line">    instance.get(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.url).toBe(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<h2 id="36-2-transform-模块单元测试"><a href="#36-2-transform-模块单元测试" class="headerlink" title="36.2 transform 模块单元测试"></a>36.2 transform 模块单元测试</h2><p><code>transform</code> 模块用来定义请求和响应的转换方法，我们需要为其编写单元测试。</p>
<p><code>test/transform.spec.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios, &#123; AxiosResponse, AxiosTransformer &#125; <span class="keyword">from</span> <span class="string">&#x27;../src/index&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; getAjaxRequest &#125; <span class="keyword">from</span> <span class="string">&#x27;./helper&#x27;</span></span><br><span class="line"></span><br><span class="line">describe(<span class="string">&#x27;transform&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  beforeEach(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    jasmine.Ajax.install()</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  afterEach(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    jasmine.Ajax.uninstall()</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should transform JSON to string&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> data = &#123;</span><br><span class="line">      foo: <span class="string">&#x27;bar&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    axios.post(<span class="string">&#x27;/foo&#x27;</span>, data)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.params).toBe(<span class="string">&#x27;&#123;&quot;foo&quot;:&quot;bar&quot;&#125;&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should transform string to JSON&#x27;</span>, <span class="function"><span class="params">done</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> response: AxiosResponse</span><br><span class="line"></span><br><span class="line">    axios(<span class="string">&#x27;/foo&#x27;</span>).then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">      response = res</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      request.respondWith(&#123;</span><br><span class="line">        status: <span class="number">200</span>,</span><br><span class="line">        responseText: <span class="string">&#x27;&#123;&quot;foo&quot;: &quot;bar&quot;&#125;&#x27;</span></span><br><span class="line">      &#125;)</span><br><span class="line"></span><br><span class="line">      <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        expect(<span class="keyword">typeof</span> response.data).toBe(<span class="string">&#x27;object&#x27;</span>)</span><br><span class="line">        expect(response.data.foo).toBe(<span class="string">&#x27;bar&#x27;</span>)</span><br><span class="line">        done()</span><br><span class="line">      &#125;, <span class="number">100</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should override default transform&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> data = &#123;</span><br><span class="line">      foo: <span class="string">&#x27;bar&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    axios.post(<span class="string">&#x27;/foo&#x27;</span>, data, &#123;</span><br><span class="line">      <span class="function"><span class="title">transformRequest</span>(<span class="params">data</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> data</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.params).toEqual(&#123; <span class="attr">foo</span>: <span class="string">&#x27;bar&#x27;</span> &#125;)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should allow an Array of transformers&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> data = &#123;</span><br><span class="line">      foo: <span class="string">&#x27;bar&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    axios.post(<span class="string">&#x27;/foo&#x27;</span>, data, &#123;</span><br><span class="line">      transformRequest: (axios.defaults.transformRequest <span class="keyword">as</span> AxiosTransformer[]).concat(<span class="function"><span class="keyword">function</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">        data</span></span></span><br><span class="line"><span class="function"><span class="params">      </span>) </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> data.replace(<span class="string">&#x27;bar&#x27;</span>, <span class="string">&#x27;baz&#x27;</span>)</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.params).toBe(<span class="string">&#x27;&#123;&quot;foo&quot;:&quot;baz&quot;&#125;&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should allowing mutating headers&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> token = <span class="built_in">Math</span>.floor(<span class="built_in">Math</span>.random() * <span class="built_in">Math</span>.pow(<span class="number">2</span>, <span class="number">64</span>)).toString(<span class="number">36</span>)</span><br><span class="line"></span><br><span class="line">    axios(<span class="string">&#x27;/foo&#x27;</span>, &#123;</span><br><span class="line">      transformRequest: <span class="function">(<span class="params">data, headers</span>) =&gt;</span> &#123;</span><br><span class="line">        headers[<span class="string">&#x27;X-Authorization&#x27;</span>] = token</span><br><span class="line">        <span class="keyword">return</span> data</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.requestHeaders[<span class="string">&#x27;X-Authorization&#x27;</span>]).toEqual(token)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<h2 id="36-3-xsrf-模块单元测试"><a href="#36-3-xsrf-模块单元测试" class="headerlink" title="36.3 xsrf 模块单元测试"></a>36.3 xsrf 模块单元测试</h2><p><code>xsrf</code> 模块提供了一套防御 <code>xsrf</code> 攻击的解决方案，我们需要为其编写单元测试。</p>
<p><code>test/xsrf.spec.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;../src/index&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; getAjaxRequest &#125; <span class="keyword">from</span> <span class="string">&#x27;./helper&#x27;</span></span><br><span class="line"></span><br><span class="line">describe(<span class="string">&#x27;xsrf&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  beforeEach(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    jasmine.Ajax.install()</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  afterEach(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    jasmine.Ajax.uninstall()</span><br><span class="line">    <span class="built_in">document</span>.cookie =</span><br><span class="line">      axios.defaults.xsrfCookieName + <span class="string">&#x27;=;expires=&#x27;</span> + <span class="keyword">new</span> <span class="built_in">Date</span>(<span class="built_in">Date</span>.now() - <span class="number">86400000</span>).toUTCString()</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should not set xsrf header if cookie is null&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    axios(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.requestHeaders[axios.defaults.xsrfHeaderName!]).toBeUndefined()</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should set xsrf header if cookie is set&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">document</span>.cookie = axios.defaults.xsrfCookieName + <span class="string">&#x27;=12345&#x27;</span></span><br><span class="line"></span><br><span class="line">    axios(<span class="string">&#x27;/foo&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.requestHeaders[axios.defaults.xsrfHeaderName!]).toBe(<span class="string">&#x27;12345&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should not set xsrf header for cross origin&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">document</span>.cookie = axios.defaults.xsrfCookieName + <span class="string">&#x27;=12345&#x27;</span></span><br><span class="line"></span><br><span class="line">    axios(<span class="string">&#x27;http://example.com/&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.requestHeaders[axios.defaults.xsrfHeaderName!]).toBeUndefined()</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should set xsrf header for cross origin when using withCredentials&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">document</span>.cookie = axios.defaults.xsrfCookieName + <span class="string">&#x27;=12345&#x27;</span></span><br><span class="line"></span><br><span class="line">    axios(<span class="string">&#x27;http://example.com/&#x27;</span>, &#123;</span><br><span class="line">      withCredentials: <span class="literal">true</span></span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.requestHeaders[axios.defaults.xsrfHeaderName!]).toBe(<span class="string">&#x27;12345&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>注意在 <code>afterEach</code> 函数中我们清空了 <code>xsrf</code> 相关的 cookie。</p>
<h2 id="36-4-上传下载模块单元测试"><a href="#36-4-上传下载模块单元测试" class="headerlink" title="36.4 上传下载模块单元测试"></a>36.4 上传下载模块单元测试</h2><p>上传下载模块允许我们监听上传和下载的进度，我们需要为其编写单元测试。</p>
<p><code>test/progress.spec.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;../src/index&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; getAjaxRequest &#125; <span class="keyword">from</span> <span class="string">&#x27;./helper&#x27;</span></span><br><span class="line"></span><br><span class="line">describe(<span class="string">&#x27;progress&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  beforeEach(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    jasmine.Ajax.install()</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  afterEach(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    jasmine.Ajax.uninstall()</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should add a download progress handler&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> progressSpy = jest.fn()</span><br><span class="line"></span><br><span class="line">    axios(<span class="string">&#x27;/foo&#x27;</span>, &#123; <span class="attr">onDownloadProgress</span>: progressSpy &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      request.respondWith(&#123;</span><br><span class="line">        status: <span class="number">200</span>,</span><br><span class="line">        responseText: <span class="string">&#x27;&#123;&quot;foo&quot;: &quot;bar&quot;&#125;&#x27;</span></span><br><span class="line">      &#125;)</span><br><span class="line">      expect(progressSpy).toHaveBeenCalled()</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should add a upload progress handler&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> progressSpy = jest.fn()</span><br><span class="line"></span><br><span class="line">    axios(<span class="string">&#x27;/foo&#x27;</span>, &#123; <span class="attr">onUploadProgress</span>: progressSpy &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="comment">// Jasmine AJAX doesn&#x27;t trigger upload events.Waiting for jest-ajax fix</span></span><br><span class="line">      <span class="comment">// expect(progressSpy).toHaveBeenCalled()</span></span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>注意，由于 <code>jasmine-ajax</code> 插件不会派发 <code>upload</code> 事件，这个未来可以通过我们自己编写的 <code>jest-ajax</code> 插件来解决，目前不写断言的情况它会直接通过。</p>
<h2 id="36-5-HTTP-授权模块单元测试"><a href="#36-5-HTTP-授权模块单元测试" class="headerlink" title="36.5 HTTP 授权模块单元测试"></a>36.5 HTTP 授权模块单元测试</h2><p>HTTP 授权模块为我们在请求头中添加 <code>Authorization</code> 字段，我们需要为其编写单元测试。</p>
<p><code>test/auth.spec.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;../src/index&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; getAjaxRequest &#125; <span class="keyword">from</span> <span class="string">&#x27;./helper&#x27;</span></span><br><span class="line"></span><br><span class="line">describe(<span class="string">&#x27;auth&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  beforeEach(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    jasmine.Ajax.install()</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  afterEach(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    jasmine.Ajax.uninstall()</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should accept HTTP Basic auth with username/password&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    axios(<span class="string">&#x27;/foo&#x27;</span>, &#123;</span><br><span class="line">      auth: &#123;</span><br><span class="line">        username: <span class="string">&#x27;Aladdin&#x27;</span>,</span><br><span class="line">        password: <span class="string">&#x27;open sesame&#x27;</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">      expect(request.requestHeaders[<span class="string">&#x27;Authorization&#x27;</span>]).toBe(<span class="string">&#x27;Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should fail to encode HTTP Basic auth credentials with non-Latin1 characters&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> axios(<span class="string">&#x27;/foo&#x27;</span>, &#123;</span><br><span class="line">      auth: &#123;</span><br><span class="line">        username: <span class="string">&#x27;Aladßç£☃din&#x27;</span>,</span><br><span class="line">        password: <span class="string">&#x27;open sesame&#x27;</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">      .then(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(</span><br><span class="line">          <span class="string">&#x27;Should not succeed to make a HTTP Basic auth request with non-latin1 chars in credentials.&#x27;</span></span><br><span class="line">        )</span><br><span class="line">      &#125;)</span><br><span class="line">      .catch(<span class="function"><span class="params">error</span> =&gt;</span> &#123;</span><br><span class="line">        expect(<span class="regexp">/character/i</span>.test(error.message)).toBeTruthy()</span><br><span class="line">      &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<h2 id="36-6-静态方法模块单元测试"><a href="#36-6-静态方法模块单元测试" class="headerlink" title="36.6 静态方法模块单元测试"></a>36.6 静态方法模块单元测试</h2><p>静态方法模块为 <code>axios</code> 对象添加了 2 个静态方法，我们需要为其编写单元测试。</p>
<p><code>test/static.spec.ts</code>：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;../src/index&#x27;</span></span><br><span class="line"></span><br><span class="line">describe(<span class="string">&#x27;promise&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  test(<span class="string">&#x27;should support all&#x27;</span>, <span class="function"><span class="params">done</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> fulfilled = <span class="literal">false</span></span><br><span class="line"></span><br><span class="line">    axios.all([<span class="literal">true</span>, <span class="literal">false</span>]).then(<span class="function"><span class="params">arg</span> =&gt;</span> &#123;</span><br><span class="line">      fulfilled = arg[<span class="number">0</span>]</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(fulfilled).toBeTruthy()</span><br><span class="line">      done()</span><br><span class="line">    &#125;, <span class="number">100</span>)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  test(<span class="string">&#x27;should support spread&#x27;</span>, <span class="function"><span class="params">done</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> sum = <span class="number">0</span></span><br><span class="line">    <span class="keyword">let</span> fulfilled = <span class="literal">false</span></span><br><span class="line">    <span class="keyword">let</span> result: <span class="built_in">any</span></span><br><span class="line"></span><br><span class="line">    axios</span><br><span class="line">      .all([<span class="number">123</span>, <span class="number">456</span>])</span><br><span class="line">      .then(</span><br><span class="line">        axios.spread(<span class="function">(<span class="params">a, b</span>) =&gt;</span> &#123;</span><br><span class="line">          sum = a + b</span><br><span class="line">          fulfilled = <span class="literal">true</span></span><br><span class="line">          <span class="keyword">return</span> <span class="string">&#x27;hello world&#x27;</span></span><br><span class="line">        &#125;)</span><br><span class="line">      )</span><br><span class="line">      .then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">        result = res</span><br><span class="line">      &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(fulfilled).toBeTruthy()</span><br><span class="line">      expect(sum).toBe(<span class="number">123</span> + <span class="number">456</span>)</span><br><span class="line">      expect(result).toBe(<span class="string">&#x27;hello world&#x27;</span>)</span><br><span class="line">      done()</span><br><span class="line">    &#125;, <span class="number">100</span>)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<h2 id="36-7-补充未覆盖的代码测试"><a href="#36-7-补充未覆盖的代码测试" class="headerlink" title="36.7 补充未覆盖的代码测试"></a>36.7 补充未覆盖的代码测试</h2><p>我们发现，跑完测试后，仍有一些代码没有覆盖到测试，其中 <code>core/xhr.ts</code> 文件的第 43 行：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (responseType) &#123;</span><br><span class="line">  request.responseType = responseType</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们并未在测试中设置过 <code>responseType</code>，因此我们在 <code>test/requests.spect.ts</code> 文件中补充相关测试：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">test(<span class="string">&#x27;should support array buffer response&#x27;</span>, <span class="function"><span class="params">done</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">let</span> response: AxiosResponse</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">str2ab</span>(<span class="params">str: <span class="built_in">string</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> buff = <span class="keyword">new</span> <span class="built_in">ArrayBuffer</span>(str.length * <span class="number">2</span>)</span><br><span class="line">    <span class="keyword">const</span> view = <span class="keyword">new</span> <span class="built_in">Uint16Array</span>(buff)</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; str.length; i++) &#123;</span><br><span class="line">      view[i] = str.charCodeAt(i)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> buff</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  axios(<span class="string">&#x27;/foo&#x27;</span>, &#123;</span><br><span class="line">    responseType: <span class="string">&#x27;arraybuffer&#x27;</span></span><br><span class="line">  &#125;).then(<span class="function"><span class="params">data</span> =&gt;</span> &#123;</span><br><span class="line">    response = data</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  getAjaxRequest().then(<span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">    request.respondWith(&#123;</span><br><span class="line">      status: <span class="number">200</span>,</span><br><span class="line">      <span class="comment">// @ts-ignore</span></span><br><span class="line">      response: str2ab(<span class="string">&#x27;Hello world&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      expect(response.data.byteLength).toBe(<span class="number">22</span>)</span><br><span class="line">      done()</span><br><span class="line">    &#125;, <span class="number">100</span>)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>另外我们发现 <code>core/xhr.ts</code> 文件的第 13 行：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">method = <span class="string">&#x27;get&#x27;</span></span><br></pre></td></tr></table></figure>

<p>分支没有测试完全。因为实际上代码执行到这的时候 <code>method</code> 是一定会有的，所以我们不必为其指定默认值，另外还需要在 <code>method!.toUpperCase()</code> 的时候使用非空断言。</p>
<p>同时<code>core/xhr.ts</code> 文件的第 66 行：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> responseData = responseType !== <span class="string">&#x27;text&#x27;</span> ? request.response : request.responseText</span><br></pre></td></tr></table></figure>

<p>分支也没有测试完全。这里我们应该先判断存在 <code>responseType</code> 存在的情况下再去和 <code>text</code> 做对比，需要修改逻辑：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> responseData = responseType &amp;&amp; responseType !== <span class="string">&#x27;text&#x27;</span> ? request.response : request.responseText</span><br></pre></td></tr></table></figure>

<p>这样再次跑测试，就覆盖了所有的分支。</p>
<p>到此为止，除了我们之前说的 <code>helpers/error.ts</code> 模块中对于 <code>super</code> 的测试的分支覆盖率没达到 100%，其它模块均达到 100% 的测试覆盖率。</p>
<p>有些有强迫症的同学可能会觉得，能不能通过某种手段让它的覆盖率达到 100% 呢，这里其实有一个奇技淫巧，在 <code>helpers/error.ts</code> 文件的 <code>constructor</code> 函数上方加一个 <code>/* istanbul ignore next */</code> 注释，这样其实相当于忽略了整个构造函数的测试，这样我们就可以达到 100% 的覆盖率了。</p>
<p><code>/* istanbul ignore next */</code> 在我们去阅读一些开源代码的时候经常会遇到，主要用途就是用来忽略测试用的，这个技巧不可滥用，除非你明确的知道这段代码不需要测试，否则你不应该使用它。滥用就失去了单元测试的意义了。</p>
<p>至此，我们就完成了整个 <code>ts-axios</code> 库的测试了，我们也成功地让测试覆盖率达到目标 99% 以上。下一章我会教大家如果打包构建和发布我们的 <code>ts-axios</code> 库。</p>
<h1 id="37-ts-axios-编译与发布"><a href="#37-ts-axios-编译与发布" class="headerlink" title="37.ts-axios 编译与发布"></a>37.ts-axios 编译与发布</h1><h2 id="37-1-需求分析"><a href="#37-1-需求分析" class="headerlink" title="37.1 需求分析"></a>37.1 需求分析</h2><p>前面的章节我们完成 <code>ts-axios</code> 库的代码编写和单元测试。这一章我们希望把代码部署发布到公共 <code>npm</code> 上，供别人下载使用。但是并不是所有人都会使用 TypeScript 开发，仍然有大量的 JavaScript 用户，它们是不能直接引用 TypeScript 代码的，因此我们需要先对源码做编译和打包，然后再发布。</p>
<p>由于我们会把包发布到公共的 npm 源，如果你还没有 <code>npm</code> 账号，那么需要先去<a target="_blank" rel="noopener" href="https://www.npmjs.com/signup">官网注册</a>。注册完成后，可以去终端执行 <code>npm login</code> 登录。这个步骤非常重要，决定你最终能否发布成功。注意，如果本地npm使用了淘宝镜像，需要切回npm官方镜像登陆<code>npm login --registry http://registry.npmjs.org</code></p>
<h2 id="37-2-编译和打包"><a href="#37-2-编译和打包" class="headerlink" title="37.2 编译和打包"></a>37.2 编译和打包</h2><p>我们会利用 <a target="_blank" rel="noopener" href="https://github.com/rollup/rollup">rollup</a> 来打包我们的 <code>ts-axios</code> 库，它是一个非常著名的编译打包工具，Vue.js 也是利用 rollup 编译打包的。相比 webpack，它非常适合去编译和打包一些 JS 库。</p>
<p>由于使用 <code>typescript-library-starter</code> 初始化我们的项目，我们已经拥有了 rollup 打包的相关配置和相关插件的安装，为了实现打包后代码的压缩，安装rollup-plugin-terser插件，</p>
<p><code>package.json</code></p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&quot;devDependencies&quot;: &#123;</span><br><span class="line">    &quot;rollup-plugin-commonjs&quot;: &quot;^9.1.8&quot;,</span><br><span class="line">    &quot;rollup-plugin-json&quot;: &quot;^3.1.0&quot;,</span><br><span class="line">    &quot;rollup-plugin-node-resolve&quot;: &quot;^3.4.0&quot;,</span><br><span class="line">    &quot;rollup-plugin-sourcemaps&quot;: &quot;^0.4.2&quot;,</span><br><span class="line">    &quot;rollup-plugin-terser&quot;: &quot;^7.0.2&quot;,</span><br><span class="line">    &quot;rollup-plugin-typescript2&quot;: &quot;^0.18.0&quot;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>接下来我们就来对生成的 <code>rollup.config.ts</code> 做小小的修改。</p>
<h3 id="37-2-1-修改-rollup-config-ts"><a href="#37-2-1-修改-rollup-config-ts" class="headerlink" title="37.2.1 修改 rollup.config.ts"></a>37.2.1 修改 rollup.config.ts</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> resolve <span class="keyword">from</span> <span class="string">&#x27;rollup-plugin-node-resolve&#x27;</span></span><br><span class="line"><span class="keyword">import</span> commonjs <span class="keyword">from</span> <span class="string">&#x27;rollup-plugin-commonjs&#x27;</span></span><br><span class="line"><span class="keyword">import</span> sourceMaps <span class="keyword">from</span> <span class="string">&#x27;rollup-plugin-sourcemaps&#x27;</span></span><br><span class="line"><span class="keyword">import</span> camelCase <span class="keyword">from</span> <span class="string">&#x27;lodash.camelcase&#x27;</span></span><br><span class="line"><span class="keyword">import</span> typescript <span class="keyword">from</span> <span class="string">&#x27;rollup-plugin-typescript2&#x27;</span></span><br><span class="line"><span class="keyword">import</span> json <span class="keyword">from</span> <span class="string">&#x27;rollup-plugin-json&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; terser &#125; <span class="keyword">from</span> <span class="string">&#x27;rollup-plugin-terser&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> pkg = <span class="built_in">require</span>(<span class="string">&#x27;./package.json&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> libraryName = <span class="string">&#x27;axios&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  input: <span class="string">`src/index.ts`</span>,</span><br><span class="line">  output: [</span><br><span class="line">    &#123; <span class="attr">file</span>: pkg.main, <span class="attr">name</span>: camelCase(libraryName), <span class="attr">format</span>: <span class="string">&#x27;umd&#x27;</span>, <span class="attr">sourcemap</span>: <span class="literal">true</span> &#125;,</span><br><span class="line">    &#123; <span class="attr">file</span>: pkg.module, <span class="attr">format</span>: <span class="string">&#x27;es&#x27;</span>, <span class="attr">sourcemap</span>: <span class="literal">true</span> &#125;</span><br><span class="line">  ],</span><br><span class="line">  <span class="comment">// Indicate here external modules you don&#x27;t wanna include in your bundle (i.e.: &#x27;lodash&#x27;)</span></span><br><span class="line">  external: [],</span><br><span class="line">  watch: &#123;</span><br><span class="line">    include: <span class="string">&#x27;src/**&#x27;</span></span><br><span class="line">  &#125;,</span><br><span class="line">  plugins: [</span><br><span class="line">    <span class="comment">// Allow json resolution</span></span><br><span class="line">    json(),</span><br><span class="line">    <span class="comment">// Compile TypeScript files</span></span><br><span class="line">    typescript(&#123;</span><br><span class="line">      typescript: <span class="built_in">require</span>(<span class="string">&#x27;typescript&#x27;</span>),</span><br><span class="line">      objectHashIgnoreUnknownHack: <span class="literal">true</span>,</span><br><span class="line">      useTsconfigDeclarationDir: <span class="literal">true</span></span><br><span class="line">    &#125;),</span><br><span class="line">    <span class="comment">// Allow bundling cjs modules (unlike webpack, rollup doesn&#x27;t understand cjs)</span></span><br><span class="line">    commonjs(),</span><br><span class="line">    <span class="comment">// Allow node_modules resolution, so you can use &#x27;external&#x27; to control</span></span><br><span class="line">    <span class="comment">// which external modules to include in the bundle</span></span><br><span class="line">    <span class="comment">// https://github.com/rollup/rollup-plugin-node-resolve#usage</span></span><br><span class="line">    resolve(),</span><br><span class="line">	terser(&#123;</span><br><span class="line">      compress: &#123;</span><br><span class="line">        pure_funcs: [<span class="string">&#x27;console.log&#x27;</span>] <span class="comment">// 去掉console.log函数</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;),</span><br><span class="line">    <span class="comment">// Resolve source maps to the original source</span></span><br><span class="line">    sourceMaps()</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>注意要修改的地方，把 <code>libraryName</code> 修改为 <code>axios</code>，<code>input</code> 修改为 <code>src/index.ts</code>。</p>
<p><code>rollup</code> 的配置很简单，我们简单地过一下。</p>
<ul>
<li>input</li>
</ul>
<p>表示打包入口文件。</p>
<ul>
<li>output</li>
</ul>
<p>表示输出的目标文件，它是一个对象数组，我们可以指定输出的格式，比如 <code>umd</code> 格式、<code>es</code> 模式等。</p>
<ul>
<li>external</li>
</ul>
<p>声明它的外部依赖，可以不被打包进去。</p>
<ul>
<li>watch</li>
</ul>
<p>监听文件的变化，重新编译，只有在编译的时候开启 <code>--watch</code> 才生效。</p>
<ul>
<li>plugins</li>
</ul>
<p>编译过程中使用的插件，其中 <code>rollup-plugin-typescript2</code> 就是用来编译 TypeScript 文件，为了防止与其他插件冲突，需要进行配置，<code>useTsconfigDeclarationDir</code> 表示使用 <code>tsconfig.json</code> 文件中定义的 <code>declarationDir</code>。其它插件感兴趣的同学可以自己去查阅文档。</p>
<h3 id="37-2-2-修改-package-json"><a href="#37-2-2-修改-package-json" class="headerlink" title="37.2.2 修改 package.json"></a>37.2.2 修改 package.json</h3><p>由于我们已经在 <code>rollup.config.ts</code> 中修改了 <code>libraryName</code> 为 <code>axios</code>， 那么在 <code>package.json</code> 文件中你需要做相关的修改：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;main&quot;</span>: <span class="string">&quot;dist/axios.umd.js&quot;</span>,</span><br><span class="line">  <span class="string">&quot;module&quot;</span>: <span class="string">&quot;dist/axios.es5.js&quot;</span>,</span><br><span class="line">  <span class="string">&quot;typings&quot;</span>: <span class="string">&quot;dist/types/index.d.ts&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>增加一个<code>tsconfig.build.json</code>配置build效果</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">&quot;compilerOptions&quot;</span>: &#123;</span><br><span class="line">    <span class="attr">&quot;types&quot;</span>: [],</span><br><span class="line">    <span class="attr">&quot;skipLibCheck&quot;</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">&quot;moduleResolution&quot;</span>: <span class="string">&quot;node&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;target&quot;</span>: <span class="string">&quot;es5&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;module&quot;</span>: <span class="string">&quot;es2015&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;lib&quot;</span>: [<span class="string">&quot;es2015&quot;</span>, <span class="string">&quot;es2016&quot;</span>, <span class="string">&quot;es2017&quot;</span>, <span class="string">&quot;dom&quot;</span>],</span><br><span class="line">    <span class="attr">&quot;strict&quot;</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">&quot;sourceMap&quot;</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">&quot;declaration&quot;</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">&quot;allowSyntheticDefaultImports&quot;</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">&quot;experimentalDecorators&quot;</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">&quot;emitDecoratorMetadata&quot;</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">&quot;declarationDir&quot;</span>: <span class="string">&quot;dist/types&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;outDir&quot;</span>: <span class="string">&quot;dist/lib&quot;</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">&quot;include&quot;</span>: [<span class="string">&quot;src&quot;</span>]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>修改 <code>package.json</code> 中<code>scripts</code>的<code>build</code></p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&quot;build&quot;: &quot;tsc -p tsconfig.build.json --module commonjs &amp;&amp; rollup -c rollup.config.ts &amp;&amp; typedoc --tsconfig tsconfig.build.json --out docs --target es6 --theme minimal --mode file src&quot;</span><br></pre></td></tr></table></figure>

<p>然后我们在控制台执行 <code>npm run build</code>，会编译输出 <code>dist</code> 目录，其中 <code>lib</code> 目录是单个 <code>.ts</code> 文件编译后的 <code>.js</code> 文件。<code>types</code> 目录是所有 <code>.ts</code> 文件编译后生产的 <code>.d.ts</code> 声明文件。<code>axios.es5.js</code> 是编译后生成的 es 模式的入口文件，用在 <code>package.json</code> 的 <code>module</code> 字段，<code>axios.umd.js</code> 文件是编译后生成的 <code>umd</code> 模式的入口文件，用在 <code>package.json</code> 的 <code>main</code> 字段。</p>
<h2 id="37-3-自动化部署"><a href="#37-3-自动化部署" class="headerlink" title="37.3 自动化部署"></a>37.3 自动化部署</h2><p>由于 <code>semantic-release</code> 插件过于黑盒也略微重量，我还是决定教同学们自己编写自动化部署脚本，这样更灵活，意义也更大，因为大部分场景是用不到那么多 feature 的。</p>
<h3 id="37-3-1-修改-package-json"><a href="#37-3-1-修改-package-json" class="headerlink" title="37.3.1 修改 package.json"></a>37.3.1 修改 package.json</h3><p>发布到 npm 之前你需要为你的包命名，由于 <code>ts-axios</code> 这个名字已经被占用了，我使用了 <code>ts-axios-hxy</code> 这个名称，当然你学到这里，就需要起一个新名字了。可以使用 <code>npm view --registry http://registry.npmjs.org [&lt;@scope&gt;/]&lt;pkg&gt;[@&lt;version&gt;]</code> 的方式去搜索一个包名是否已经存在，比如你搜索 <code>npm view --registry http://registry.npmjs.org ts-axios</code> 会发现这个包已经存在，返回这个包相关信息。如果你搜索 <code>npm view --registry http://registry.npmjs.org xxxx</code> 返回错误 404 的话，那么你就可以使用 <code>xxxx</code> 这个包名了。</p>
<p>如果你想让你发布的包关联你的仓库地址，可以配置 <code>repository</code> 的 <code>url</code> 字段。</p>
<p>另外我们增加 2 个 npm scripts：</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">&quot;prepub&quot;</span>: <span class="string">&quot;npm run test:prod &amp;&amp; npm run build&quot;</span>,</span><br><span class="line">  <span class="attr">&quot;pub&quot;</span>: <span class="string">&quot;sh release.sh&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>当我们运行 <code>npm run pub</code> 的时候，会优先执行 <code>prepub</code> 脚本，在 <code>prepub</code> 中我们运行了 <code>test:prod</code> 和 <code>build</code> 2 个脚本。<code>&amp;&amp;</code> 符号表示前面一个命令执行成功后才会执行后面的任务。</p>
<p><code>npm run test:prod</code> 实际上运行了 <code>npm run lint &amp;&amp; npm run test -- --no-cache</code>。 先运行 <code>lint</code> 去校验我们的源码和测试文件是否遵循 <code>tslint</code> 规范，再运行 <code>test</code> 去跑测试。</p>
<p><code>npm run build</code> 实际上运行了 <code>tsc --module commonjs</code>、<code>rollup -c rollup.config.ts</code> 和 <code>typedoc --out docs --target es6 --theme minimal --mode file src</code>。先运行 <code>tsc</code> 去编译我们的 <code>TypeScript</code> 文件，<code>dist/lib</code> 和 <code>dist/types</code> 下的文件就是该命令产生的，然后运行 <code>rollup</code> 去构建 <code>axios.umd.js</code> 及 <code>axios.es.js</code>，最后运行 <code>typedoc</code> 去构建项目的文档。</p>
<p>运行完 <code>prepub</code> 后就会再运行 <code>pub</code> 命令，实际上执行了 <code>sh release.sh</code> 命令，但是目前我们没有这个脚本，接下来我们就需要来编写部署脚本 <code>release.sh</code>。</p>
<h3 id="37-3-2-编写部署脚本"><a href="#37-3-2-编写部署脚本" class="headerlink" title="37.3.2 编写部署脚本"></a>37.3.2 编写部署脚本</h3><p><code>release.sh</code>：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/usr/bin/env sh</span></span><br><span class="line"><span class="built_in">set</span> -e</span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Enter release version: &quot;</span></span><br><span class="line"><span class="built_in">read</span> VERSION</span><br><span class="line"><span class="built_in">read</span> -p <span class="string">&quot;Releasing <span class="variable">$VERSION</span> - are you sure? (y/n)&quot;</span> -n 1 -r</span><br><span class="line"><span class="built_in">echo</span>  <span class="comment"># (optional) move to a new line</span></span><br><span class="line"><span class="keyword">if</span> [[ <span class="variable">$REPLY</span> =~ ^[Yy]$ ]]</span><br><span class="line"><span class="keyword">then</span></span><br><span class="line">  <span class="built_in">echo</span> <span class="string">&quot;Releasing <span class="variable">$VERSION</span> ...&quot;</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># commit</span></span><br><span class="line">  git add -A</span><br><span class="line">  git commit -m <span class="string">&quot;[build] <span class="variable">$VERSION</span>&quot;</span></span><br><span class="line">  npm version <span class="variable">$VERSION</span> --message <span class="string">&quot;[release] <span class="variable">$VERSION</span>&quot;</span></span><br><span class="line">  git push origin master</span><br><span class="line">  npm config <span class="built_in">set</span> registry=http://registry.npmjs.org</span><br><span class="line"></span><br><span class="line">  <span class="comment"># publish</span></span><br><span class="line">  npm publish</span><br><span class="line">  npm run deploy-docs</span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure>

<p>部署脚本是 shell 脚本，shell 脚本就是封装了多行控制台命令，来逐行解释他们的含义。</p>
<p><code>#!/usr/bin/env sh</code> 用来表示它是一个 shell 脚本。</p>
<p><code>set -e</code> 告诉脚本如果执行结果不为 true 则退出。</p>
<p><code>echo &quot;Enter release version: &quot;</code> 在控制台输出 <code>Enter release version:</code>。</p>
<p><code>read VERSION</code> 表示从标准输入读取值，并赋值给 $VERSION 变量。</p>
<p><code>read -p &quot;Releasing $VERSION - are you sure? (y/n)&quot; -n 1 -r</code>，其中 <code>read -p</code> 表示给出提示符，后面接着 <code>Releasing $VERSION - are you sure? (y/n)</code> 提示符；<code>-n 1</code> 表示限定最多可以有 1 个字符可以作为有效读入；<code>-r</code> 表示禁止反斜线的转义功能。因为我们的 read 并没有指定变量名，那么默认这个输入读取值会赋值给 <code>$REPLY</code> 变量。</p>
<p><code>echo</code> 输出空值表示跳到一个新行，<code>#</code> 在 shell 脚本中表示注释。</p>
<p><code>if [[ $REPLY =~ ^[Yy]$ ]]</code> 表示 shell 脚本中的流程控制语句，判断 <code>$REPLY</code> 是不是大小写的 <code>y</code>，如果满足，则走到后面的 <code>then</code> 逻辑。</p>
<p><code>echo &quot;Releasing $VERSION ...&quot;</code> 在控制台输出 <code>Releasing $VERSION ...</code>。</p>
<p><code>git add -A</code> 表示把代码所有变化提交到暂存区。</p>
<p><code>git commit -m &quot;[build] $VERSION&quot;</code> 表示提交代码，提交注释是 <code>[build] $VERSION</code>。</p>
<p><code>npm version $VERSION --message &quot;[release] $VERSION&quot;</code> 是修改 <code>package.json</code> 中的 <code>version</code> 字段到 <code>$VERSION</code>，并且提交一条修改记录，提交注释是 <code>[release] $VERSION</code>。</p>
<p><code>git push origin master</code> 是把代码发布到主干分支。</p>
<p><code>npm publish</code> 是把仓库发布到 <code>npm</code> 上，我们会把 <code>dist</code> 目录下的代码都发布到 <code>npm</code> 上，因为我们在 <code>package.json</code> 中配置的是 <code>files</code> 是 <code>[&quot;dist&quot;]</code>。</p>
<p><code>npm run deploy-docs</code>是自动部署网页文档，可以利用github-pages实现文档的在线阅读，我直接简单粗暴修改了<code>tools\gh-pages-publish.ts</code>里的文件</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; cd, exec, echo, touch &#125; = <span class="built_in">require</span>(<span class="string">&quot;shelljs&quot;</span>)</span><br><span class="line"></span><br><span class="line">echo(<span class="string">&quot;Deploying docs!!!&quot;</span>)</span><br><span class="line">cd(<span class="string">&quot;docs&quot;</span>)</span><br><span class="line">touch(<span class="string">&quot;.nojekyll&quot;</span>)</span><br><span class="line">exec(<span class="string">&quot;git init&quot;</span>)</span><br><span class="line">exec(<span class="string">&quot;git add .&quot;</span>)</span><br><span class="line">exec(<span class="string">&#x27;git config user.name &quot;&quot;&#x27;</span>)</span><br><span class="line">exec(<span class="string">&#x27;git config user.email &quot;&quot;&#x27;</span>)</span><br><span class="line">exec(<span class="string">&#x27;git commit -m &quot;docs(docs): update gh-pages&quot;&#x27;</span>)</span><br><span class="line">exec(</span><br><span class="line">  <span class="string">`git push --force --quiet &quot;git@github.com:<span class="subst">$&#123;yourname&#125;</span>/ts-axios.git&quot; master:gh-pages`</span></span><br><span class="line">)</span><br><span class="line">echo(<span class="string">&quot;Docs deployed!!&quot;</span>)</span><br></pre></td></tr></table></figure>



<h2 id="37-4-运行部署脚本"><a href="#37-4-运行部署脚本" class="headerlink" title="37.4 运行部署脚本"></a>37.4 运行部署脚本</h2><p>接下来我们就运行 <code>npm run pub</code> 脚本部署，我们会发现在 <code>npm run prepub</code> 阶段，在执行 <code>tslint --project tsconfig.json -t codeFrame &#39;src/**/*.ts&#39; &#39;test/**/*.ts&#39;</code> 的时候失败了，原因是我们有代码不符合 lint 规范。原来是 <code>core/xhr.ts</code> 文件中 <code>processCancel</code> 函数中对 <code>promise</code> 的处理，我们没有对异常情况处理，所以我们要给它加上 <code>catch</code> 的逻辑：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">processCancel</span>(<span class="params"></span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (cancelToken) &#123;</span><br><span class="line">    cancelToken.promise</span><br><span class="line">      .then(<span class="function"><span class="params">reason</span> =&gt;</span> &#123;</span><br><span class="line">        request.abort()</span><br><span class="line">        reject(reason)</span><br><span class="line">      &#125;)</span><br><span class="line">      .catch(</span><br><span class="line">        <span class="comment">/* istanbul ignore next */</span></span><br><span class="line">        () =&gt; &#123;</span><br><span class="line">        <span class="comment">// do nothing</span></span><br><span class="line">      &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>由于我们不会走到 <code>catch</code> 逻辑，所以我们给它添加一个注释 <code>/* istanbul ignore next */</code> 忽略该代码分支的测试。</p>
<p>然后我们再重新运行 <code>npm run pub</code> 逻辑，它会先执行 <code>test</code>，然后运行 <code>build</code> 编译代码，再执行 <code>release.sh</code> 脚本。我们输入了要发布的版本，它就可以完成了整个代码的发布流程。</p>
<p>通过编写部署脚本的一行命令发布的方式，不仅可以用在这种 JS 库，也可以用于我们平时项目开发中，可以大大帮助我们提高生产率，也是前端工程化中必不可少的一个环节，希望同学们都能学会并掌握它。</p>
<p>至此我们完成了项目的部署和发布，我们也可以在 <code>npm</code> 官网上看到我们发布的包，下一节课我们来创建一个实际项目，来引用我们开发的 <code>ts-axios</code> 库。</p>
<h1 id="37-引用-ts-axios-库"><a href="#37-引用-ts-axios-库" class="headerlink" title="37.引用 ts-axios 库"></a>37.引用 ts-axios 库</h1><h2 id="37-1-在-TS-项目中引用"><a href="#37-1-在-TS-项目中引用" class="headerlink" title="37.1 在 TS 项目中引用"></a>37.1 在 TS 项目中引用</h2><p>我们借助于 <a target="_blank" rel="noopener" href="https://cli.vuejs.org/">vue-cli</a> 脚手架创建一个 TypeScript 的 Vue 项目，然后我们把 Vue 官网上一段使用 axios 发请求的 <a target="_blank" rel="noopener" href="https://cn.vuejs.org/v2/guide/computed.html#%E4%BE%A6%E5%90%AC%E5%99%A8">demo</a> 代码抄过来。</p>
<p>我们需要先执行 <code>npm install ts-axios-hxy</code>   安装 <code>ts-axios</code> 库。</p>
<p>执行 <code>npm install lodash</code>   安装 <code>lodash</code> 库并导入</p>
<p><code>shims-tsx.d.ts</code></p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="built_in">module</span> <span class="string">&quot;lodash&quot;</span> &#123;</span><br><span class="line">  <span class="keyword">import</span> lodash <span class="keyword">from</span> <span class="string">&quot;lodash&quot;</span>;</span><br><span class="line">  <span class="keyword">export</span> <span class="keyword">default</span> lodash;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>HelloWorld.vue</code></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br></pre></td><td class="code"><pre><span class="line">&lt;template&gt;</span><br><span class="line">  &lt;div class&#x3D;&quot;hello&quot;&gt;</span><br><span class="line">    &lt;p&gt;</span><br><span class="line">      Ask a yes&#x2F;no question:</span><br><span class="line">      &lt;input v-model&#x3D;&quot;question&quot; &#x2F;&gt;</span><br><span class="line">    &lt;&#x2F;p&gt;</span><br><span class="line">    &lt;p&gt;&#123;&#123; answer &#125;&#125;&lt;&#x2F;p&gt;</span><br><span class="line">  &lt;&#x2F;div&gt;</span><br><span class="line">&lt;&#x2F;template&gt;</span><br><span class="line"></span><br><span class="line">&lt;script lang&#x3D;&quot;ts&quot;&gt;</span><br><span class="line">import Vue from &quot;vue&quot;;</span><br><span class="line">import _ from &quot;lodash&quot;;</span><br><span class="line">import axios from &quot;ts-axios-hxy&quot;;</span><br><span class="line"></span><br><span class="line">export default Vue.extend(&#123;</span><br><span class="line">  name: &quot;HelloWorld&quot;,</span><br><span class="line">  data() &#123;</span><br><span class="line">    return &#123;</span><br><span class="line">      question: &quot;&quot;,</span><br><span class="line">      answer: &quot;I cannot give you an answer until you ask a question!&quot;,</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;,</span><br><span class="line">  created() &#123;</span><br><span class="line">    this.debouncedGetAnswer &#x3D; _.debounce(this.getAnswer, 500);</span><br><span class="line">  &#125;,</span><br><span class="line">  methods: &#123;</span><br><span class="line">    debouncedGetAnswer() &#123;</span><br><span class="line">      &#x2F;&#x2F; do nothing</span><br><span class="line">    &#125;,</span><br><span class="line">    getAnswer() &#123;</span><br><span class="line">      if (this.question.indexOf(&quot;?&quot;) &#x3D;&#x3D;&#x3D; -1) &#123;</span><br><span class="line">        this.answer &#x3D; &quot;Questions usually contain a question mark. -)&quot;;</span><br><span class="line">        return;</span><br><span class="line">      &#125;</span><br><span class="line">      this.answer &#x3D; &quot;Thinking...&quot;;</span><br><span class="line">      const instance &#x3D; axios.create();</span><br><span class="line">      instance.interceptors.request.use((config) &#x3D;&gt; &#123;</span><br><span class="line">        config.params &#x3D; &#123;</span><br><span class="line">          _t: +new Date(),</span><br><span class="line">        &#125;;</span><br><span class="line">        return config;</span><br><span class="line">      &#125;);</span><br><span class="line"></span><br><span class="line">      instance</span><br><span class="line">        .get(&quot;https:&#x2F;&#x2F;yesno.wtf&#x2F;api&quot;)</span><br><span class="line">        .then((response) &#x3D;&gt; &#123;</span><br><span class="line">          this.answer &#x3D; _.capitalize(response.data.answer);</span><br><span class="line">        &#125;)</span><br><span class="line">        .catch((error) &#x3D;&gt; &#123;</span><br><span class="line">          this.answer &#x3D; &quot;Error! Could not reach the API. &quot; + error;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">  watch: &#123;</span><br><span class="line">    question: function (newQuestion: string, oldQuestion: string) &#123;</span><br><span class="line">      this.answer &#x3D; &quot;Waiting for you to stop typing...&quot;;</span><br><span class="line">      this.debouncedGetAnswer();</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br><span class="line">&lt;&#x2F;script&gt;</span><br><span class="line"></span><br><span class="line">&lt;!-- Add &quot;scoped&quot; attribute to limit CSS to this component only --&gt;</span><br><span class="line">&lt;style scoped&gt;</span><br><span class="line">h3 &#123;</span><br><span class="line">  margin: 40px 0 0;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">ul &#123;</span><br><span class="line">  list-style-type: none;</span><br><span class="line">  padding: 0;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">li &#123;</span><br><span class="line">  display: inline-block;</span><br><span class="line">  margin: 0 10px;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">a &#123;</span><br><span class="line">  color: #42b983;</span><br><span class="line">&#125;</span><br><span class="line">&lt;&#x2F;style&gt;</span><br></pre></td></tr></table></figure>

<p>这段代码主要是提供了一个 <code>input</code> 输入框，绑定了 <code>question</code> 变量，当我们输入的时候，会触发 <code>question</code> 的变化，执行 <code>watch question</code> 中的逻辑，执行 <code>this.debouncedGetAnswer</code> 方法，实际上就是 <code>debounce</code> 执行了 <code>getAnswer</code> 方法，发送请求。</p>
<p>我们通过 <code>import axios from &#39;ts-axios-hxy&#39; </code> 去加载 <code>ts-axios</code> 库，实际上就是引入了 <code>node_modules/ts-axios-hxy/dist/axios.es5.js</code>，因为 <code>ts-axios-hxy</code> 的 <code>package.json</code> 文件中配置的 <code>module</code> 字段是 <code>dist/axios.es5.js</code>，在 <code>webpack</code> 中优先 <code>import</code> 优先会找 <code>module</code> 字段，其次是 <code>main</code> 字段。</p>
<blockquote>
<p>小技巧：当我们引入某个库运行时出现问题时候，我们就可以调试 node_modules 中对应引入的代码。</p>
</blockquote>
<p>注意我们这里先使用了 <code>axios.create()</code> 方法创建了一个 <code>instance</code>，然后添加了一个请求拦截器，会在每次发送请求前，添加了一个 <code>_t</code> 参数，值为时间戳。然后执行 <code>instance.get</code> 发送一个请求。</p>
<p>我们可以看到整个 demo 是可以正常运行的，并且没有任何类型相关的问题，说明我们的库打包后的代码和类型声明文件都是没有问题的。</p>
<h2 id="37-2-在-JS-项目中引用"><a href="#37-2-在-JS-项目中引用" class="headerlink" title="37.2 在 JS 项目中引用"></a>37.2 在 JS 项目中引用</h2><p>我们编写的 TS 库仍然可以被纯 JS 的项目引用，这次我们来修改<a target="_blank" rel="noopener" href="https://coding.imooc.com/class/74.html">《Vue.js2.5+cube-ui重构饿了么App》</a>课程的代码，把之前对 <code>axios</code> 的引用改成对 <code>ts-axios-new</code> 的引用。课程源码是开源的，所以没购买课程的小伙伴也可以去 <a target="_blank" rel="noopener" href="https://github.com/ustbhuangyi/vue-sell">GitHub</a> 下载。</p>
<p>我们需要先执行 <code>npm install ts-axios-hxy</code> 安装 <code>ts-axios</code> 库，然后修改代码。</p>
<p>只需要把所有的</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;axios&#x27;</span></span><br></pre></td></tr></table></figure>

<p>修改为</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;ts-axios-hxy&#x27;</span></span><br></pre></td></tr></table></figure>

<p>以及使用cdn的</p>
<p><code>public/index.html</code></p>
<p>中的cdn链接替换为</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://unpkg.com/ts-axios-hxy/dist/axios.es5.js&quot;</span> <span class="attr">type</span>=<span class="string">&quot;module&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>即可。</p>
<p>接着运行项目，我们发现项目可以成功运行，因为我们实现了<code>axios</code> 在浏览器端的所有功能，所以可以放心的做替换。</p>
<p>至此，我们就完成了 <code>ts-axios</code> 库的开发、测试、编译、发布和引用。课程到这里也就告一段落了，下一章我们会对整个课程做总结与展望。</p>
<h1 id="课程回顾与总结-原作者的话"><a href="#课程回顾与总结-原作者的话" class="headerlink" title="课程回顾与总结(原作者的话)"></a>课程回顾与总结(原作者的话)</h1><p>这节课我们主要是对课程做回顾与总结，总共分为以下几个方面。</p>
<ul>
<li>TypeScript 基础知识</li>
</ul>
<p>希望同学们学习后能掌握 TypeScript 的基础语法知识，并能灵活运用在实际项目中。学这部分知识除了看课程视频外，一定要多去翻 TypeScript 的官网文档（英文）。另外，对于一些课程中没有提到的语法知识，感兴趣的同学可以做延伸学习，并了解它们的使用场景。</p>
<ul>
<li>ts-axios 实战</li>
</ul>
<p>希望同学们学习后不仅能学会使用 TypeScript 做开发，还要对 axios 的实现原理，接触到的 HTTP 等知识做巩固学习。课程文档中有很多文档链接，希望同学们可以对这些知识做延伸学习，查漏补缺。另外，同学们要学会模块化地编程思想，要学会写干净的代码，把一些编程技巧和思想运用在自己的实际工作中。</p>
<ul>
<li>单元测试</li>
</ul>
<p>希望同学们可以学会单元测试的编写，并养成写测试的习惯。另外可以对 Jest 测试框架做进一步延伸学习，并尝试在自己的项目中编写测试。</p>
<ul>
<li>编译和部署</li>
</ul>
<p>希望同学们可以对编译和部署的流程了解，掌握 webpack、rollup 等打包工具。并尝试在自己的项目中编写部署脚本，争取做到一键发布。</p>
<ul>
<li>课程展望</li>
</ul>
<p>未来我会开发 jest-ajax 插件，会以文章的方式告诉大家如何编写 Jest 的插件，并替换 jasmine-ajax 插件。另外也会更新 ts-axios 在 node.js 端的实现，会更新视频和电子书。</p>
<p>另外如果对 Vue.js 开发感兴趣的同学，也可以关注我的 Vue 三部曲：<a target="_blank" rel="noopener" href="https://coding.imooc.com/class/74.html">Vue.js2.5+cube-ui重构饿了么App（中级）</a>   -&gt; <a target="_blank" rel="noopener" href="https://coding.imooc.com/class/107.html">Vue2.0开发企业级移动端音乐Web App（高级）</a> -&gt; <a target="_blank" rel="noopener" href="https://coding.imooc.com/class/228.html">Vue.js源码全方位深入解析（高级）</a>。未来随着 Vue.js 升级到 3.0，这些课程也会做同步更新（音乐课程和源码课程会重新录制），所以完全不用担心版本升级问题，也希望大家都能够支持我的正版课程。</p>
<p>最后，祝愿同学们都能学有所成，精进技术，少加班，拿高薪。</p>

    </div>

    
    
    
      
<div>
        <div style="text-align:center;color: #ccc;font-size:14px;">-------------本文结束<i class="fa fa-paw"></i>感谢您的阅读-------------</div>
</div>
        

  <div class="followme">
    <p>欢迎关注我的其它发布渠道</p>

    <div class="social-list">

        <div class="social-item">
          <a target="_blank" class="social-link" href="/atom.xml">
            <span class="icon">
              <i class="fa fa-rss"></i>
            </span>

            <span class="label">RSS</span>
          </a>
        </div>
    </div>
  </div>


      <footer class="post-footer">
          <div class="post-tags">
              <a href="/tags/%E9%A1%B9%E7%9B%AE/" rel="tag"><i class="fa fa-tag"></i> 项目</a>
              <a href="/tags/Typescript/" rel="tag"><i class="fa fa-tag"></i> Typescript</a>
              <a href="/tags/axios/" rel="tag"><i class="fa fa-tag"></i> axios</a>
          </div>

        


        
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/2021/05/11/%E8%AF%A6%E8%A7%A3ES6%E4%B8%AD%E7%9A%84class/" rel="prev" title="详解ES6中的class">
      <i class="fa fa-chevron-left"></i> 详解ES6中的class
    </a></div>
      <div class="post-nav-item">
    <a href="/2021/05/30/%E5%89%8D%E7%AB%AF%E4%B8%8E%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86/" rel="next" title="前端与编译原理">
      前端与编译原理 <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          
    <div class="comments" id="valine-comments"></div>

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%89%B9%E6%80%A7"><span class="nav-text">特性</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#1-%E5%88%9D%E5%A7%8B%E5%8C%96%E9%A1%B9%E7%9B%AE"><span class="nav-text">1.初始化项目</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#1-1-%E5%88%9B%E5%BB%BA%E4%BB%A3%E7%A0%81%E4%BB%93%E5%BA%93"><span class="nav-text">1.1 创建代码仓库</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#1-2-TypeScript-library-starter"><span class="nav-text">1.2 TypeScript library starter</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#1-2-1-%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F"><span class="nav-text">1.2.1 使用方式</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#1-2-2-%E7%9B%AE%E5%BD%95%E6%96%87%E4%BB%B6%E4%BB%8B%E7%BB%8D"><span class="nav-text">1.2.2 目录文件介绍</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#1-2-3-%E4%BC%98%E7%A7%80%E5%B7%A5%E5%85%B7%E9%9B%86%E6%88%90"><span class="nav-text">1.2.3 优秀工具集成</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#1-2-4-Npm-Scripts"><span class="nav-text">1.2.4 Npm Scripts</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#1-3-%E5%85%B3%E8%81%94%E8%BF%9C%E7%A8%8B%E5%88%86%E6%94%AF"><span class="nav-text">1.3 关联远程分支</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#1-3-1-%E6%8B%89%E5%8F%96%E4%BB%A3%E7%A0%81"><span class="nav-text">1.3.1 拉取代码</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#1-3-2-%E6%8F%90%E4%BA%A4%E4%BB%A3%E7%A0%81"><span class="nav-text">1.3.2 提交代码</span></a></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#2-%E7%BC%96%E5%86%99%E5%9F%BA%E7%A1%80%E8%AF%B7%E6%B1%82%E4%BB%A3%E7%A0%81"><span class="nav-text">2.编写基础请求代码</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#2-1-%E5%88%9B%E5%BB%BA%E5%85%A5%E5%8F%A3%E6%96%87%E4%BB%B6"><span class="nav-text">2.1 创建入口文件</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#2-1-1-%E7%BC%96%E8%AF%91%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6-tsconfig-json"><span class="nav-text">2.1.1 编译配置文件 tsconfig.json</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-1-2-%E5%AE%9A%E4%B9%89-AxiosRequestConfig-%E6%8E%A5%E5%8F%A3%E7%B1%BB%E5%9E%8B"><span class="nav-text">2.1.2 定义 AxiosRequestConfig 接口类型</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#2-2-%E5%88%A9%E7%94%A8-XMLHttpRequest-%E5%8F%91%E9%80%81%E8%AF%B7%E6%B1%82"><span class="nav-text">2.2 利用 XMLHttpRequest 发送请求</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#2-2-1-%E5%BC%95%E5%85%A5-xhr-%E6%A8%A1%E5%9D%97"><span class="nav-text">2.2.1 引入 xhr 模块</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#2-3-demo-%E7%BC%96%E5%86%99"><span class="nav-text">2.3 demo 编写</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#2-3-1-%E4%BE%9D%E8%B5%96%E5%AE%89%E8%A3%85"><span class="nav-text">2.3.1 依赖安装</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-3-2-%E7%BC%96%E5%86%99-webpack-%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6"><span class="nav-text">2.3.2 编写 webpack 配置文件</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-3-3-%E7%BC%96%E5%86%99-server-%E6%96%87%E4%BB%B6"><span class="nav-text">2.3.3 编写 server 文件</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-3-4-%E7%BC%96%E5%86%99-demo-%E4%BB%A3%E7%A0%81"><span class="nav-text">2.3.4 编写 demo 代码</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-3-5-%E8%BF%90%E8%A1%8C-demo"><span class="nav-text">2.3.5 运行 demo</span></a></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#3-%E5%A4%84%E7%90%86%E8%AF%B7%E6%B1%82-url-%E5%8F%82%E6%95%B0"><span class="nav-text">3.处理请求 url 参数</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#3-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">3.1 需求分析</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#3-1-1-%E5%8F%82%E6%95%B0%E5%80%BC%E4%B8%BA%E6%95%B0%E7%BB%84"><span class="nav-text">3.1.1 参数值为数组</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#3-1-2-%E5%8F%82%E6%95%B0%E5%80%BC%E4%B8%BA%E5%AF%B9%E8%B1%A1"><span class="nav-text">3.1.2 参数值为对象</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#3-1-3-%E5%8F%82%E6%95%B0%E5%80%BC%E4%B8%BA-Date-%E7%B1%BB%E5%9E%8B"><span class="nav-text">3.1.3 参数值为 Date 类型</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#3-1-4-%E7%89%B9%E6%AE%8A%E5%AD%97%E7%AC%A6%E6%94%AF%E6%8C%81"><span class="nav-text">3.1.4 特殊字符支持</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#3-1-5-%E7%A9%BA%E5%80%BC%E5%BF%BD%E7%95%A5"><span class="nav-text">3.1.5 空值忽略</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#3-1-6-%E4%B8%A2%E5%BC%83-url-%E4%B8%AD%E7%9A%84%E5%93%88%E5%B8%8C%E6%A0%87%E8%AE%B0"><span class="nav-text">3.1.6 丢弃 url 中的哈希标记</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#3-1-7-%E4%BF%9D%E7%95%99-url-%E4%B8%AD%E5%B7%B2%E5%AD%98%E5%9C%A8%E7%9A%84%E5%8F%82%E6%95%B0"><span class="nav-text">3.1.7 保留 url 中已存在的参数</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#3-2-buildURL-%E5%87%BD%E6%95%B0%E5%AE%9E%E7%8E%B0"><span class="nav-text">3.2 buildURL 函数实现</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#3-3-%E5%AE%9E%E7%8E%B0-url-%E5%8F%82%E6%95%B0%E5%A4%84%E7%90%86%E9%80%BB%E8%BE%91"><span class="nav-text">3.3 实现 url 参数处理逻辑</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#3-4-demo-%E7%BC%96%E5%86%99"><span class="nav-text">3.4 demo 编写</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#4-%E5%A4%84%E7%90%86%E8%AF%B7%E6%B1%82-body-%E6%95%B0%E6%8D%AE"><span class="nav-text">4.处理请求 body 数据</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#4-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">4.1 需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#4-2-transformRequest-%E5%87%BD%E6%95%B0%E5%AE%9E%E7%8E%B0"><span class="nav-text">4.2 transformRequest 函数实现</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#4-3-%E5%AE%9E%E7%8E%B0%E8%AF%B7%E6%B1%82-body-%E5%A4%84%E7%90%86%E9%80%BB%E8%BE%91"><span class="nav-text">4.3 实现请求 body 处理逻辑</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#4-4-%E7%BC%96%E5%86%99-demo"><span class="nav-text">4.4 编写 demo</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#5-%E5%A4%84%E7%90%86%E8%AF%B7%E6%B1%82-header"><span class="nav-text">5.处理请求 header</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#5-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">5.1 需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#5-2-processHeaders-%E5%87%BD%E6%95%B0%E5%AE%9E%E7%8E%B0"><span class="nav-text">5.2 processHeaders 函数实现</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#5-3-%E5%AE%9E%E7%8E%B0%E8%AF%B7%E6%B1%82-header-%E5%A4%84%E7%90%86%E9%80%BB%E8%BE%91"><span class="nav-text">5.3 实现请求 header 处理逻辑</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#5-4-demo-%E7%BC%96%E5%86%99"><span class="nav-text">5.4 demo 编写</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#6-%E8%8E%B7%E5%8F%96%E5%93%8D%E5%BA%94%E6%95%B0%E6%8D%AE"><span class="nav-text">6.获取响应数据</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#6-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">6.1 需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#6-2-%E5%AE%9A%E4%B9%89%E6%8E%A5%E5%8F%A3%E7%B1%BB%E5%9E%8B"><span class="nav-text">6.2 定义接口类型</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#6-3-%E5%AE%9E%E7%8E%B0%E8%8E%B7%E5%8F%96%E5%93%8D%E5%BA%94%E6%95%B0%E6%8D%AE%E9%80%BB%E8%BE%91"><span class="nav-text">6.3 实现获取响应数据逻辑</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#6-4-demo-%E7%BC%96%E5%86%99"><span class="nav-text">6.4 demo 编写</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#7-%E5%A4%84%E7%90%86%E5%93%8D%E5%BA%94-header"><span class="nav-text">7.处理响应 header</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#7-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">7.1 需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#7-2-parseHeaders-%E5%87%BD%E6%95%B0%E5%AE%9E%E7%8E%B0%E5%8F%8A%E5%BA%94%E7%94%A8"><span class="nav-text">7.2 parseHeaders 函数实现及应用</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#8-%E5%A4%84%E7%90%86%E5%93%8D%E5%BA%94-data"><span class="nav-text">8.处理响应 data</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#8-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">8.1 需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#8-2-transformResponse-%E5%87%BD%E6%95%B0%E5%AE%9E%E7%8E%B0%E5%8F%8A%E5%BA%94%E7%94%A8"><span class="nav-text">8.2 transformResponse 函数实现及应用</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#9-%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86"><span class="nav-text">9.错误处理</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#9-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">9.1 需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#9-2-%E5%A4%84%E7%90%86%E7%BD%91%E7%BB%9C%E5%BC%82%E5%B8%B8%E9%94%99%E8%AF%AF"><span class="nav-text">9.2 处理网络异常错误</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#9-3-%E5%A4%84%E7%90%86%E8%B6%85%E6%97%B6%E9%94%99%E8%AF%AF"><span class="nav-text">9.3 处理超时错误</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#9-4-%E5%A4%84%E7%90%86%E9%9D%9E-200-%E7%8A%B6%E6%80%81%E7%A0%81"><span class="nav-text">9.4 处理非 200 状态码</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#9-5-demo-%E7%BC%96%E5%86%99"><span class="nav-text">9.5 demo 编写</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#10-%E9%94%99%E8%AF%AF%E4%BF%A1%E6%81%AF%E5%A2%9E%E5%BC%BA"><span class="nav-text">10.错误信息增强</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#10-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">10.1 需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#10-2-%E5%88%9B%E5%BB%BA-AxiosError-%E7%B1%BB"><span class="nav-text">10.2 创建 AxiosError 类</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#10-3-createError-%E6%96%B9%E6%B3%95%E5%BA%94%E7%94%A8"><span class="nav-text">10.3 createError 方法应用</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#10-4-%E5%AF%BC%E5%87%BA%E7%B1%BB%E5%9E%8B%E5%AE%9A%E4%B9%89"><span class="nav-text">10.4 导出类型定义</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#11-%E6%89%A9%E5%B1%95%E6%8E%A5%E5%8F%A3"><span class="nav-text">11.扩展接口</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#11-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">11.1 需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#11-2-%E6%8E%A5%E5%8F%A3%E7%B1%BB%E5%9E%8B%E5%AE%9A%E4%B9%89"><span class="nav-text">11.2 接口类型定义</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#11-3-%E5%88%9B%E5%BB%BA-Axios-%E7%B1%BB"><span class="nav-text">11.3 创建 Axios 类</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#11-4-%E6%B7%B7%E5%90%88%E5%AF%B9%E8%B1%A1%E5%AE%9E%E7%8E%B0"><span class="nav-text">11.4 混合对象实现</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#11-5-demo-%E7%BC%96%E5%86%99"><span class="nav-text">11.5 demo 编写</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#12-axios-%E5%87%BD%E6%95%B0%E9%87%8D%E8%BD%BD"><span class="nav-text">12.axios 函数重载</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#12-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">12.1 需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#12-2-%E9%87%8D%E8%BD%BD%E5%AE%9E%E7%8E%B0"><span class="nav-text">12.2 重载实现</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#12-3-%E7%BC%96%E5%86%99-demo"><span class="nav-text">12.3 编写 demo</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#13-%E5%93%8D%E5%BA%94%E6%95%B0%E6%8D%AE%E6%94%AF%E6%8C%81%E6%B3%9B%E5%9E%8B"><span class="nav-text">13.响应数据支持泛型</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#13-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">13.1 需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#13-2-%E6%8E%A5%E5%8F%A3%E6%B7%BB%E5%8A%A0%E6%B3%9B%E5%9E%8B%E5%8F%82%E6%95%B0"><span class="nav-text">13.2 接口添加泛型参数</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#13-3-demo-%E7%BC%96%E5%86%99"><span class="nav-text">13.3 demo 编写</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#14-%E6%8B%A6%E6%88%AA%E5%99%A8%E8%AE%BE%E8%AE%A1%E4%B8%8E%E5%AE%9E%E7%8E%B0"><span class="nav-text">14.拦截器设计与实现</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#14-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">14.1 需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#14-2-%E6%95%B4%E4%BD%93%E8%AE%BE%E8%AE%A1"><span class="nav-text">14.2 整体设计</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#14-3-%E6%8B%A6%E6%88%AA%E5%99%A8%E7%AE%A1%E7%90%86%E7%B1%BB%E5%AE%9E%E7%8E%B0"><span class="nav-text">14.3 拦截器管理类实现</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#14-3-1-%E6%8E%A5%E5%8F%A3%E5%AE%9A%E4%B9%89"><span class="nav-text">14.3.1 接口定义</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#14-3-2-%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0"><span class="nav-text">14.3.2 代码实现</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#14-4-%E9%93%BE%E5%BC%8F%E8%B0%83%E7%94%A8%E5%AE%9E%E7%8E%B0"><span class="nav-text">14.4 链式调用实现</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#14-5-demo-%E7%BC%96%E5%86%99"><span class="nav-text">14.5 demo 编写</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#15-%E5%90%88%E5%B9%B6%E9%85%8D%E7%BD%AE%E7%9A%84%E8%AE%BE%E8%AE%A1%E4%B8%8E%E5%AE%9E%E7%8E%B0"><span class="nav-text">15.合并配置的设计与实现</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#15-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">15.1 需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#15-2-%E9%BB%98%E8%AE%A4%E9%85%8D%E7%BD%AE"><span class="nav-text">15.2 默认配置</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#15-2-1-%E9%BB%98%E8%AE%A4%E9%85%8D%E7%BD%AE%E5%AE%9A%E4%B9%89"><span class="nav-text">15.2.1 默认配置定义</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#15-2-2-%E6%B7%BB%E5%8A%A0%E5%88%B0-axios-%E5%AF%B9%E8%B1%A1%E4%B8%AD"><span class="nav-text">15.2.2 添加到 axios 对象中</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#15-3-%E9%85%8D%E7%BD%AE%E5%90%88%E5%B9%B6%E5%8F%8A%E7%AD%96%E7%95%A5"><span class="nav-text">15.3 配置合并及策略</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#15-3-1-%E5%90%88%E5%B9%B6%E6%96%B9%E6%B3%95"><span class="nav-text">15.3.1 合并方法</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#15-3-2-%E9%BB%98%E8%AE%A4%E5%90%88%E5%B9%B6%E7%AD%96%E7%95%A5"><span class="nav-text">15.3.2 默认合并策略</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#15-3-3-%E5%8F%AA%E6%8E%A5%E5%8F%97%E8%87%AA%E5%AE%9A%E4%B9%89%E9%85%8D%E7%BD%AE%E5%90%88%E5%B9%B6%E7%AD%96%E7%95%A5"><span class="nav-text">15.3.3 只接受自定义配置合并策略</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#15-3-4-%E5%A4%8D%E6%9D%82%E5%AF%B9%E8%B1%A1%E5%90%88%E5%B9%B6%E7%AD%96%E7%95%A5"><span class="nav-text">15.3.4 复杂对象合并策略</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#15-4-flatten-headers"><span class="nav-text">15.4 flatten headers</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#15-5-demo-%E7%BC%96%E5%86%99"><span class="nav-text">15.5 demo 编写</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#16-%E8%AF%B7%E6%B1%82%E5%92%8C%E5%93%8D%E5%BA%94%E9%85%8D%E7%BD%AE%E5%8C%96"><span class="nav-text">16.请求和响应配置化</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#16-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">16.1 需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#16-2-%E4%BF%AE%E6%94%B9%E9%BB%98%E8%AE%A4%E9%85%8D%E7%BD%AE"><span class="nav-text">16.2 修改默认配置</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#16-3-transform-%E9%80%BB%E8%BE%91%E9%87%8D%E6%9E%84"><span class="nav-text">16.3 transform 逻辑重构</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#16-4-demo-%E7%BC%96%E5%86%99"><span class="nav-text">16.4 demo 编写</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#17-%E6%89%A9%E5%B1%95-axios-create-%E9%9D%99%E6%80%81%E6%8E%A5%E5%8F%A3"><span class="nav-text">17.扩展 axios.create 静态接口</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#17-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">17.1 需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#17-2-%E9%9D%99%E6%80%81%E6%96%B9%E6%B3%95%E6%89%A9%E5%B1%95"><span class="nav-text">17.2 静态方法扩展</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#17-3-demo-%E7%BC%96%E5%86%99"><span class="nav-text">17.3 demo 编写</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#18-%E5%8F%96%E6%B6%88%E5%8A%9F%E8%83%BD%E7%9A%84%E8%AE%BE%E8%AE%A1%E4%B8%8E%E5%AE%9E%E7%8E%B0"><span class="nav-text">18.取消功能的设计与实现</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#18-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">18.1 需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#18-2-%E5%BC%82%E6%AD%A5%E5%88%86%E7%A6%BB%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%96%B9%E6%A1%88"><span class="nav-text">18.2 异步分离的设计方案</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#18-3-CancelToken-%E7%B1%BB%E5%AE%9E%E7%8E%B0"><span class="nav-text">18.3 CancelToken 类实现</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#18-3-1-%E6%8E%A5%E5%8F%A3%E5%AE%9A%E4%B9%89"><span class="nav-text">18.3.1 接口定义</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#18-3-2-%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0"><span class="nav-text">18.3.2 代码实现</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#18-4-CancelToken-%E6%89%A9%E5%B1%95%E9%9D%99%E6%80%81%E6%8E%A5%E5%8F%A3"><span class="nav-text">18.4 CancelToken 扩展静态接口</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#18-4-1-%E6%8E%A5%E5%8F%A3%E5%AE%9A%E4%B9%89"><span class="nav-text">18.4.1 接口定义</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#18-4-2-%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0"><span class="nav-text">18.4.2 代码实现</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#18-5-Cancel-%E7%B1%BB%E5%AE%9E%E7%8E%B0%E5%8F%8A-axios-%E7%9A%84%E6%89%A9%E5%B1%95"><span class="nav-text">18.5 Cancel 类实现及 axios 的扩展</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#18-5-1-%E6%8E%A5%E5%8F%A3%E5%AE%9A%E4%B9%89"><span class="nav-text">18.5.1 接口定义</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#18-5-2-%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0"><span class="nav-text">18.5.2 代码实现</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#18-6-%E9%A2%9D%E5%A4%96%E9%80%BB%E8%BE%91%E5%AE%9E%E7%8E%B0"><span class="nav-text">18.6 额外逻辑实现</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#18-7-demo-%E7%BC%96%E5%86%99"><span class="nav-text">18.7 demo 编写</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#19-withCredentials"><span class="nav-text">19.withCredentials</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#19-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">19.1 需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#19-2-%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0"><span class="nav-text">19.2 代码实现</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#19-3-demo-%E7%BC%96%E5%86%99"><span class="nav-text">19.3 demo 编写</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#20-XSRF-%E9%98%B2%E5%BE%A1"><span class="nav-text">20.XSRF 防御</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#20-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">20.1 需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#20-2-%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0"><span class="nav-text">20.2 代码实现</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#20-3-demo-%E7%BC%96%E5%86%99"><span class="nav-text">20.3 demo 编写</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#21-%E4%B8%8A%E4%BC%A0%E5%92%8C%E4%B8%8B%E8%BD%BD%E7%9A%84%E8%BF%9B%E5%BA%A6%E7%9B%91%E6%8E%A7"><span class="nav-text">21.上传和下载的进度监控</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#21-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">21.1 需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#21-2-%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0"><span class="nav-text">21.2 代码实现</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#21-3-demo-%E7%BC%96%E5%86%99"><span class="nav-text">21.3 demo 编写</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#22-HTTP-%E6%8E%88%E6%9D%83"><span class="nav-text">22.HTTP 授权</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#22-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">22.1 需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#22-2-%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0"><span class="nav-text">22.2 代码实现</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#22-3-demo-%E7%BC%96%E5%86%99"><span class="nav-text">22.3 demo 编写</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#23-%E8%87%AA%E5%AE%9A%E4%B9%89%E5%90%88%E6%B3%95%E7%8A%B6%E6%80%81%E7%A0%81"><span class="nav-text">23.自定义合法状态码</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#23-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">23.1 需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#23-2-%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0"><span class="nav-text">23.2 代码实现</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#23-3-demo-%E7%BC%96%E5%86%99"><span class="nav-text">23.3 demo 编写</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#24-%E8%87%AA%E5%AE%9A%E4%B9%89%E5%8F%82%E6%95%B0%E5%BA%8F%E5%88%97%E5%8C%96"><span class="nav-text">24.自定义参数序列化</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#24-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">24.1 需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#24-2-%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0"><span class="nav-text">24.2 代码实现</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#24-3-demo-%E7%BC%96%E5%86%99"><span class="nav-text">24.3 demo 编写</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#25-baseURL"><span class="nav-text">25.baseURL</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#25-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">25.1 需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#25-2-%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0"><span class="nav-text">25.2 代码实现</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#25-3-demo-%E7%BC%96%E5%86%99"><span class="nav-text">25.3 demo 编写</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#26-%E9%9D%99%E6%80%81%E6%96%B9%E6%B3%95%E6%89%A9%E5%B1%95"><span class="nav-text">26.静态方法扩展</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#26-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">26.1 需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#26-2-%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0"><span class="nav-text">26.2 代码实现</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#26-3-demo-%E7%BC%96%E5%86%99"><span class="nav-text">26.3 demo 编写</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#27-%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E5%89%8D%E8%A8%80"><span class="nav-text">27.单元测试前言</span></a></li><li class="nav-item nav-level-1"><a class="nav-link" href="#28-Jest-%E5%AE%89%E8%A3%85%E5%92%8C%E9%85%8D%E7%BD%AE"><span class="nav-text">28.Jest 安装和配置</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#28-1-Jest-%E5%AE%89%E8%A3%85"><span class="nav-text">28.1 Jest 安装</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#28-2-Jest-%E9%85%8D%E7%BD%AE"><span class="nav-text">28.2 Jest 配置</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#29-%E8%BE%85%E5%8A%A9%E6%A8%A1%E5%9D%97%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95"><span class="nav-text">29.辅助模块单元测试</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#29-1-%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C"><span class="nav-text">29.1 准备工作</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#29-2-util-%E6%A8%A1%E5%9D%97%E6%B5%8B%E8%AF%95"><span class="nav-text">29.2 util 模块测试</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#29-3-cookie-%E6%A8%A1%E5%9D%97%E6%B5%8B%E8%AF%95"><span class="nav-text">29.3 cookie 模块测试</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#29-4-data-%E6%A8%A1%E5%9D%97%E6%B5%8B%E8%AF%95"><span class="nav-text">29.4 data 模块测试</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#29-5-error-%E6%A8%A1%E5%9D%97%E6%B5%8B%E8%AF%95"><span class="nav-text">29.5 error 模块测试</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#29-6-headers-%E6%A8%A1%E5%9D%97%E6%B5%8B%E8%AF%95"><span class="nav-text">29.6 headers 模块测试</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#29-7-url-%E6%A8%A1%E5%9D%97%E6%B5%8B%E8%AF%95"><span class="nav-text">29.7 url 模块测试</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#30-%E8%AF%B7%E6%B1%82%E6%A8%A1%E5%9D%97%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95"><span class="nav-text">30.请求模块单元测试</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#30-1-jasmine-ajax"><span class="nav-text">30.1 jasmine-ajax</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#30-2-%E6%B5%8B%E8%AF%95%E4%BB%A3%E7%A0%81%E7%BC%96%E5%86%99"><span class="nav-text">30.2 测试代码编写</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#31-headers-%E6%A8%A1%E5%9D%97%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95"><span class="nav-text">31.headers 模块单元测试</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#31-1-%E6%B5%8B%E8%AF%95%E4%BB%A3%E7%A0%81%E7%BC%96%E5%86%99"><span class="nav-text">31.1 测试代码编写</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#32-Axios-%E5%AE%9E%E4%BE%8B%E6%A8%A1%E5%9D%97%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95"><span class="nav-text">32.Axios 实例模块单元测试</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#32-1-%E6%B5%8B%E8%AF%95%E4%BB%A3%E7%A0%81%E7%BC%96%E5%86%99"><span class="nav-text">32.1 测试代码编写</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#33-%E6%8B%A6%E6%88%AA%E5%99%A8%E6%A8%A1%E5%9D%97%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95"><span class="nav-text">33.拦截器模块单元测试</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#33-1-%E6%B5%8B%E8%AF%95%E4%BB%A3%E7%A0%81%E7%BC%96%E5%86%99"><span class="nav-text">33.1 测试代码编写</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#34-mergeConfig-%E6%A8%A1%E5%9D%97%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95"><span class="nav-text">34.mergeConfig 模块单元测试</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#34-1-%E6%B5%8B%E8%AF%95%E4%BB%A3%E7%A0%81%E7%BC%96%E5%86%99"><span class="nav-text">34.1 测试代码编写</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#35-%E8%AF%B7%E6%B1%82%E5%8F%96%E6%B6%88%E6%A8%A1%E5%9D%97%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95"><span class="nav-text">35.请求取消模块单元测试</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#35-1-Cancel-%E7%B1%BB%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95"><span class="nav-text">35.1 Cancel 类单元测试</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#35-2-CancelToken-%E7%B1%BB%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95"><span class="nav-text">35.2 CancelToken 类单元测试</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#35-3-Cancel-%E4%B8%9A%E5%8A%A1%E9%80%BB%E8%BE%91%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95"><span class="nav-text">35.3 Cancel 业务逻辑单元测试</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#36-%E5%89%A9%E4%BD%99%E6%A8%A1%E5%9D%97%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95"><span class="nav-text">36.剩余模块单元测试</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#36-1-defaults-%E6%A8%A1%E5%9D%97%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95"><span class="nav-text">36.1 defaults 模块单元测试</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#36-2-transform-%E6%A8%A1%E5%9D%97%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95"><span class="nav-text">36.2 transform 模块单元测试</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#36-3-xsrf-%E6%A8%A1%E5%9D%97%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95"><span class="nav-text">36.3 xsrf 模块单元测试</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#36-4-%E4%B8%8A%E4%BC%A0%E4%B8%8B%E8%BD%BD%E6%A8%A1%E5%9D%97%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95"><span class="nav-text">36.4 上传下载模块单元测试</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#36-5-HTTP-%E6%8E%88%E6%9D%83%E6%A8%A1%E5%9D%97%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95"><span class="nav-text">36.5 HTTP 授权模块单元测试</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#36-6-%E9%9D%99%E6%80%81%E6%96%B9%E6%B3%95%E6%A8%A1%E5%9D%97%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95"><span class="nav-text">36.6 静态方法模块单元测试</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#36-7-%E8%A1%A5%E5%85%85%E6%9C%AA%E8%A6%86%E7%9B%96%E7%9A%84%E4%BB%A3%E7%A0%81%E6%B5%8B%E8%AF%95"><span class="nav-text">36.7 补充未覆盖的代码测试</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#37-ts-axios-%E7%BC%96%E8%AF%91%E4%B8%8E%E5%8F%91%E5%B8%83"><span class="nav-text">37.ts-axios 编译与发布</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#37-1-%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90"><span class="nav-text">37.1 需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#37-2-%E7%BC%96%E8%AF%91%E5%92%8C%E6%89%93%E5%8C%85"><span class="nav-text">37.2 编译和打包</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#37-2-1-%E4%BF%AE%E6%94%B9-rollup-config-ts"><span class="nav-text">37.2.1 修改 rollup.config.ts</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#37-2-2-%E4%BF%AE%E6%94%B9-package-json"><span class="nav-text">37.2.2 修改 package.json</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#37-3-%E8%87%AA%E5%8A%A8%E5%8C%96%E9%83%A8%E7%BD%B2"><span class="nav-text">37.3 自动化部署</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#37-3-1-%E4%BF%AE%E6%94%B9-package-json"><span class="nav-text">37.3.1 修改 package.json</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#37-3-2-%E7%BC%96%E5%86%99%E9%83%A8%E7%BD%B2%E8%84%9A%E6%9C%AC"><span class="nav-text">37.3.2 编写部署脚本</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#37-4-%E8%BF%90%E8%A1%8C%E9%83%A8%E7%BD%B2%E8%84%9A%E6%9C%AC"><span class="nav-text">37.4 运行部署脚本</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#37-%E5%BC%95%E7%94%A8-ts-axios-%E5%BA%93"><span class="nav-text">37.引用 ts-axios 库</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#37-1-%E5%9C%A8-TS-%E9%A1%B9%E7%9B%AE%E4%B8%AD%E5%BC%95%E7%94%A8"><span class="nav-text">37.1 在 TS 项目中引用</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#37-2-%E5%9C%A8-JS-%E9%A1%B9%E7%9B%AE%E4%B8%AD%E5%BC%95%E7%94%A8"><span class="nav-text">37.2 在 JS 项目中引用</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E8%AF%BE%E7%A8%8B%E5%9B%9E%E9%A1%BE%E4%B8%8E%E6%80%BB%E7%BB%93-%E5%8E%9F%E4%BD%9C%E8%80%85%E7%9A%84%E8%AF%9D"><span class="nav-text">课程回顾与总结(原作者的话)</span></a></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="hxy"
      src="/images/Robben.gif">
  <p class="site-author-name" itemprop="name">hxy</p>
  <div class="site-description" itemprop="description"></div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">80</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
          
        <span class="site-state-item-count">8</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">120</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="https://github.com/huxingyi1997" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;huxingyi1997" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
      </span>
      <span class="links-of-author-item">
        <a href="mailto:huxingyi1997@zju.edu.cn" title="E-Mail → mailto:huxingyi1997@zju.edu.cn" rel="noopener" target="_blank"><i class="fa fa-envelope fa-fw"></i>E-Mail</a>
      </span>
  </div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-frog"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">hxy</span>
</div>

<div class="theme-info">
  <div class="powered-by"></div>
  <span class="post-count">博客全站共1039.2k字</span>
</div>

        
<div class="busuanzi-count">
  <script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
    <span class="post-meta-item" id="busuanzi_container_site_uv" style="display: none;">
      <span class="post-meta-item-icon">
        <i class="fa fa-user"></i>
      </span>
      <span class="site-uv" title="总访客量">
        <span id="busuanzi_value_site_uv"></span>
      </span>
    </span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item" id="busuanzi_container_site_pv" style="display: none;">
      <span class="post-meta-item-icon">
        <i class="fa fa-eye"></i>
      </span>
      <span class="site-pv" title="总访问量">
        <span id="busuanzi_value_site_pv"></span>
      </span>
    </span>
</div>








      </div>
    </footer>
  </div>

  
  <script src="/lib/anime.min.js"></script>
  <script src="//cdn.jsdelivr.net/npm/lozad@1/dist/lozad.min.js"></script>
  <script src="/lib/velocity/velocity.min.js"></script>
  <script src="/lib/velocity/velocity.ui.min.js"></script>

<script src="/js/utils.js"></script>

<script src="/js/motion.js"></script>


<script src="/js/schemes/pisces.js"></script>


<script src="/js/next-boot.js"></script>




  




  
<script src="/js/local-search.js"></script>













  

  


<script>
NexT.utils.loadComments(document.querySelector('#valine-comments'), () => {
  NexT.utils.getScript('//unpkg.com/valine/dist/Valine.min.js', () => {
    var GUEST = ['nick', 'mail', 'link'];
    var guest = 'nick,mail,link';
    guest = guest.split(',').filter(item => {
      return GUEST.includes(item);
    });
    new Valine({
      el         : '#valine-comments',
      verify     : false,
      notify     : true,
      appId      : 'pQsO3ySbU4VtWN2j1FLA74Ha-gzGzoHsz',
      appKey     : 'QYacMDY2VY7Wazprg1X6FiUv',
      placeholder: "Just go go",
      avatar     : 'mm',
      meta       : guest,
      pageSize   : '10' || 10,
      visitor    : false,
      lang       : 'zh-cn' || 'zh-cn',
      path       : location.pathname,
      recordIP   : false,
      serverURLs : ''
    });
  }, window.Valine);
});
</script>

  
  <!-- 动态背景特效 -->
  <!-- 樱花特效 -->
    <script async src="/js/src/sakura.js"></script>
    <script async src="/js/src/fairyDustCursor.js"></script>
</body>
</html>
